// 版权所有2011 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 包加载包。
package load

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"go/build"
	"go/scanner"
	"go/token"
	"internal/goroot"
	"io/fs"
	"os"
	"path"
	pathpkg "path"
	"path/filepath"
	"runtime"
	"runtime/debug"
	"sort"
	"strconv"
	"strings"
	"time"
	"unicode"
	"unicode/utf8"

	"cmd/go/internal/base"
	"cmd/go/internal/cfg"
	"cmd/go/internal/fsys"
	"cmd/go/internal/imports"
	"cmd/go/internal/modfetch"
	"cmd/go/internal/modinfo"
	"cmd/go/internal/modload"
	"cmd/go/internal/par"
	"cmd/go/internal/search"
	"cmd/go/internal/str"
	"cmd/go/internal/trace"
	"cmd/go/internal/vcs"
	"cmd/internal/sys"

	"golang.org/x/mod/modfile"
	"golang.org/x/mod/module"
)

// 包描述在目录中找到的单个包。
type Package struct {
	PackagePublic                 // 在“go列表”中可见
	Internal      PackageInternal // 仅在go命令内部使用
}

type PackagePublic struct {
	// 注意：这些字段是go命令的公共API的一部分。
	// 见列表。去可以添加字段，但不能更改或删除现有字段。与列表保持同步。go 
	Dir           string                `json:",omitempty"` // 包含包源的目录
	ImportPath    string                `json:",omitempty"` // 目录中包的导入路径
	ImportComment string                `json:",omitempty"` // 导入对包语句的注释中的路径
	Name          string                `json:",omitempty"` // 包名
	Doc           string                `json:",omitempty"` // 包文档字符串
	Target        string                `json:",omitempty"` // 此包的安装目标（可能可执行）
	Shlib         string                `json:",omitempty"` // 包含此文件的共享库package（仅在-linkshared时设置）
	Root          string                `json:",omitempty"` // 根目录，路径目录，或包含此程序包的模块根目录
	ConflictDir   string                `json:",omitempty"` // 目录被另一个目录
	ForTest       string                `json:",omitempty"` // 程序包仅用于命名测试
	Export        string                `json:",omitempty"` // 包含导出数据的文件（按go列表设置-导出）
	BuildID       string                `json:",omitempty"` // 编译程序包的构建ID（按go列表设置-导出）
	Module        *modinfo.ModulePublic `json:",omitempty"` // 关于程序包模块的信息，如果有任何
	Match         []string              `json:",omitempty"` // 命令行模式匹配此包
	Goroot        bool                  `json:",omitempty"` // 是否在根目录中找到此包？
	Standard      bool                  `json:",omitempty"` // 这个包是标准Go库的一部分吗？
	DepOnly       bool                  `json:",omitempty"` // 程序包仅作为依赖项，未明确列出
	BinaryOnly    bool                  `json:",omitempty"` // 无法重新编译程序包
	Incomplete    bool                  `json:",omitempty"` // 加载此程序包或依赖项时出错吗？

	// 对于list命令，Stale和StaleReason保留在这里*仅*。
	// 它们只是在为列表执行做准备时初始化的。
	// 在操作执行期间，常规构建会动态确定过时性。
	Stale       bool   `json:",omitempty"` // ‘go install’会为这个软件包做任何事情吗？
	StaleReason string `json:",omitempty"` // 为什么Stale是真的？

	// 源文件
	// 如果您添加到此列表中，您也必须添加到p.AllFiles（以下）中。
	// 否则，文件名安全列表将不适用于任何新增内容。
	GoFiles           []string `json:",omitempty"` // /。go源文件（不包括CgoFiles、TestGoFiles、XTestGoFiles）
	CgoFiles          []string `json:",omitempty"` // /。转到导入“C”
	CompiledGoFiles   []string `json:",omitempty"` // 的源文件。在CgoFiles 
	IgnoredGoFiles    []string `json:",omitempty"` // 上运行cgo的go输出。由于生成限制
	InvalidGoFiles    []string `json:",omitempty"` // /，go源文件被忽略。转到检测到问题的源文件（解析错误、错误的包名等）
	IgnoredOtherFiles []string `json:",omitempty"` // non-。由于构建限制
	CFiles            []string `json:",omitempty"` // /，go源文件被忽略。c源文件
	CXXFiles          []string `json:",omitempty"` // /。抄送。cpp和。cxx源文件
	MFiles            []string `json:",omitempty"` // /。m源文件
	HFiles            []string `json:",omitempty"` // /。H啊。水电站和。hxx源文件
	FFiles            []string `json:",omitempty"` // /。FF为了和。f90 Fortran源文件
	SFiles            []string `json:",omitempty"` // /。s源文件
	SwigFiles         []string `json:",omitempty"` // /。swig文件
	SwigCXXFiles      []string `json:",omitempty"` // /。swigcxx文件
	SysoFiles         []string `json:",omitempty"` // /。DEFG 


	CgoLDFLAGS   []string `json:",omitempty"` // cgo:链接器标志
	CgoPkgConfig []string `json:",omitempty"` // cgo:pkg配置名称

	// 依赖项信息
	Imports   []string          `json:",omitempty"` // 此包使用的导入路径
	ImportMap map[string]string `json:",omitempty"` // 从源导入映射到导入路径（省略标识项）
	Deps      []string          `json:",omitempty"` // 所有（递归）导入的依赖项

	// 错误信息
	// 上面是不完整的，打包到其他文件中
	Error      *PackageError   `json:",omitempty"` // 加载此包时出错（非依赖项）
	DepsErrors []*PackageError `json:",omitempty"` // 加载依赖项时出错

	// 测试信息
	// 如果添加到此列表中，您也必须添加到p.AllFiles（下面）中。
	// 否则，文件名安全列表将不适用于任何新增内容。
	TestGoFiles        []string `json:",omitempty"` // /_测试。go文件包
	TestImports        []string `json:",omitempty"` // 从TestGoFiles 
	TestEmbedPatterns  []string `json:",omitempty"` // 
	TestEmbedFiles     []string `json:",omitempty"` // 被测试模式匹配的文件
	XTestGoFiles       []string `json:",omitempty"` // /\u测试导入。go文件包外
	XTestImports       []string `json:",omitempty"` // 从XTESTGO文件导入
	XTestEmbedPatterns []string `json:",omitempty"` // 
	XTestEmbedFiles    []string `json:",omitempty"` // 由XTestEmbedPatterns匹配的文件
}

// AllFiles返回包中考虑的所有文件的名称。
// 这是用于健康和安全检查，所以我们包括所有文件，
// go/build包过滤掉了其他包（比如foo_errogoarch.s）
// 没关系。
func (p *Package) AllFiles() []string {
	files := str.StringList(
		p.GoFiles,
		p.CgoFiles,
		// 没有p.CompiledProfiles，因为它们来自GoFiles或由我们生成
		p.IgnoredGoFiles,
		// 没有p.InvalidGoFiles，因为它们来自GoFiles 
		p.IgnoredOtherFiles,
		p.CFiles,
		p.CXXFiles,
		p.MFiles,
		p.HFiles,
		p.FFiles,
		p.SFiles,
		p.SwigFiles,
		p.SwigCXXFiles,
		p.SysoFiles,
		p.TestGoFiles,
		p.XTestGoFiles,
	)

	// 嵌入文件可能与其他文件重叠。
	// 重复数据消除，但尽可能长时间延迟构建地图。
	// 只需对照上述文件变量检查当前目录中的文件（名称中没有斜杠）
	// 即可。
	var have map[string]bool
	for _, file := range p.EmbedFiles {
		if !strings.Contains(file, "/") {
			if have == nil {
				have = make(map[string]bool)
				for _, file := range files {
					have[file] = true
				}
			}
			if have[file] {
				continue
			}
		}
		files = append(files, file)
	}
	return files
}

// Desc返回包“description”，用于b.showOutput。
func (p *Package) Desc() string {
	if p.ForTest != "" {
		return p.ImportPath + " [" + p.ForTest + ".test]"
	}
	return p.ImportPath
}

// 始终报告p是否是仅测试的包。
// 
// “仅测试”包是指：
// -是普通包的仅测试变体，或
// -是测试二进制文件的合成“主”包，或
// -仅包含_test。去查档案。
func (p *Package) IsTestOnly() bool {
	return p.ForTest != "" ||
		p.Internal.TestmainGo != nil ||
		len(p.TestGoFiles)+len(p.XTestGoFiles) > 0 && len(p.GoFiles)+len(p.CgoFiles) == 0
}

type PackageInternal struct {
	// 未报告的字段不属于公共API。
	Build             *build.Package
	Imports           []*Package           // 此软件包的直接导入
	CompiledImports   []string             // 使用CompiledProfiles时需要的其他导入（全部来自标准库）；1:1，以PackagePublic结尾。导入
	RawImports        []string             // 程序文本中显示的此程序包的原始导入；1:1，以PackagePublic结尾。导入
	ForceLibrary      bool                 // 此程序包是一个库（即使名为“main”）
	CmdlineFiles      bool                 // 程序包由命令行上列出的文件构建
	CmdlinePkg        bool                 // 命令行上列出的程序包
	CmdlinePkgLiteral bool                 // 命令行上列出为文本的程序包（不通过通配符）
	Local             bool                 // 通过本地路径（/或../）导入
	LocalPrefix       string               // 解释。
	ExeName           string               // 临时可执行文件的所需名称
	FuzzInstrument    bool                 // 应检测包以模糊
	CoverMode         string               // 在此模式下使用coverage tool预处理Go源文件
	CoverVars         map[string]*CoverVar // 由coverage analysis 
	OmitDebug         bool                 // 创建的变量告诉链接器不要写入调试信息
	GobinSubdir       bool                 // 安装目标是GOBIN 
	BuildInfo         string               // 将此信息添加到包主
	TestmainGo        *[]byte              // 用于_testmain的内容。go 
	Embed             map[string][]string  // 
	OrigImportPath    string               // 添加“_test”后缀

	Asmflags   []string // -asmlags之前的原始导入路径
	Gcflags    []string // -gcflags for this package 
	Ldflags    []string // -ldflags for this package 
	Gccgoflags []string // -gccgolfags for this package 
}

// 无错误表明该包的go文件适用于
// 为该软件包构建。
// 
// 这可能是因为没有任何文件，或者因为所有文件都被排除，或者因为所有未排除的文件都是测试源。
type NoGoError struct {
	Package *Package
}

func (e *NoGoError) Error() string {
	if len(e.Package.IgnoredGoFiles) > 0 {
		// Go文件存在，但由于构建限制而被忽略。
		return "build constraints exclude all Go files in " + e.Package.Dir
	}
	if len(e.Package.TestGoFiles)+len(e.Package.XTestGoFiles) > 0 {
		// 测试Go文件存在，但我们对它们不感兴趣。
		// 双负片很不幸，但我们想要电子包装。Dir 
		// 将出现在错误消息的末尾。
		return "no non-test Go files in " + e.Package.Dir
	}
	return "no Go files in " + e.Package.Dir
}

// setLoadPackageDataError将在加载包数据时发现的错误
// 作为*PackageError。它对一些常见错误有特殊情况，以改进
// 向用户显示的消息，并减少冗余。
// 
// setLoadPackageDataError如果加载有关
// 导入的包的信息是安全的，则返回true，例如，如果在一个文件中加载导入
// 时出现解析错误，但其他文件没有问题。
func (p *Package) setLoadPackageDataError(err error, path string, stk *ImportStack, importPos []token.Position) {
	matchErr, isMatchErr := err.(*search.MatchError)
	if isMatchErr && matchErr.Match.Pattern() == path {
		if matchErr.Match.IsLiteral() {
			// 错误有一个模式，该模式与导入路径类似。
			// 可能稍有不同（./foo matching example.com/foo），但距离足够近，看起来有些多余。
			// 打开错误，这样我们就不会显示模式。
			err = matchErr.Err
		}
	}

	// 替换（可能已包装）*构建。没有错误，带*负载。没有错。
	// 后者更具体地说明了原因。
	var nogoErr *build.NoGoError
	if errors.As(err, &nogoErr) {
		if p.Dir == "" && nogoErr.Dir != "" {
			p.Dir = nogoErr.Dir
		}
		err = &NoGoError{Package: p}
	}

	// 仅从扫描仪中获取第一个错误。错误列表。PackageError only 
	// 有一个位置的空间，因此我们报告第一个位置为
	// 的错误，而不是所有没有位置的错误。
	var pos string
	var isScanErr bool
	if scanErr, ok := err.(scanner.ErrorList); ok && len(scanErr) > 0 {
		isScanErr = true // 用于下面的堆栈推送/弹出。

		scanPos := scanErr[0].Pos
		scanPos.Filename = base.ShortPath(scanPos.Filename)
		pos = scanPos.String()
		err = errors.New(scanErr[0].Msg)
	}

	// 如果问题在于导入声明
	// 例如，如果包不存在或导入路径不正确，请在导入包上报告错误。
	// 另一方面，如果问题在于导入的软件包，请不要包含职位，例如没有Go文件（无错误），或者导入的
	// 包的源文件本身（扫描仪错误）。
	// 
	// TODO（matloob）：可能会在第一组
	// （包括modload.importmissingeror、ImportMissingSumError和
	// 中的任何一个生成的GOPATH模式错误
	// 中都找不到包%q。（*上下文）。进口modload。模棱两可的导入错误、
	// 和modload。包装模块错误；以及golang中产生的错误模块路径错误
	// 。org/x/mod/module。CheckMod）实现一个接口
	// 以便更容易地检查它们？这将使我们不必将modload错误移到这个包中，以避免包导入周期，也不必为生成中产生的错误导出错误类型。
	if !isMatchErr && (nogoErr != nil || isScanErr) {
		stk.Push(path)
		defer stk.Pop()
	}

	p.Error = &PackageError{
		ImportStack: stk.Copy(),
		Pos:         pos,
		Err:         err,
	}

	if path != stk.Top() {
		p.Error.setPos(importPos)
	}
}

// Resolve返回导入的解析版本，
// 应该是p.TestImports或p.XTestImports，而不是p.imports。
// 在p.TestImports和p.XTestImports中的导入不是在p的初始加载期间递归加载的
// 因此它们列出了在源文件
// 中找到的导入，但大多数处理应该通过供应商解析的
// 导入路径进行。我们懒散地执行此解决方案是为了避免文件系统工作
// 并且因为测试导入的最终实际负载（在“go test”期间）
// 如果从原始路径开始，可以生成更好的错误消息。
// p的初始加载加载所有非测试导入，并重写
// vendored路径，因此不应调用p.vendored（p.imports）。
func (p *Package) Resolve(imports []string) []string {
	if len(imports) > 0 && len(p.Imports) > 0 && &imports[0] == &p.Imports[0] {
		panic("internal error: p.Resolve(p.Imports) called")
	}
	seen := make(map[string]bool)
	var all []string
	for _, path := range imports {
		path = ResolveImportPath(p, path)
		if !seen[path] {
			seen[path] = true
			all = append(all, path)
		}
	}
	sort.Strings(all)
	return all
}

// CoverVar保存针对指定文件生成的覆盖率变量的名称。
type CoverVar struct {
	File string // 本地文件名
	Var  string // 计数结构名
}

func (p *Package) copyBuild(opts PackageOpts, pp *build.Package) {
	p.Internal.Build = pp

	if pp.PkgTargetRoot != "" && cfg.BuildPkgdir != "" {
		old := pp.PkgTargetRoot
		pp.PkgRoot = cfg.BuildPkgdir
		pp.PkgTargetRoot = cfg.BuildPkgdir
		pp.PkgObj = filepath.Join(cfg.BuildPkgdir, strings.TrimPrefix(pp.PkgObj, old))
	}

	p.Dir = pp.Dir
	p.ImportPath = pp.ImportPath
	p.ImportComment = pp.ImportComment
	p.Name = pp.Name
	p.Doc = pp.Doc
	p.Root = pp.Root
	p.ConflictDir = pp.ConflictDir
	p.BinaryOnly = pp.BinaryOnly

	// TODO？目标
	p.Goroot = pp.Goroot
	p.Standard = p.Goroot && p.ImportPath != "" && search.IsStandardImportPath(p.ImportPath)
	p.GoFiles = pp.GoFiles
	p.CgoFiles = pp.CgoFiles
	p.IgnoredGoFiles = pp.IgnoredGoFiles
	p.InvalidGoFiles = pp.InvalidGoFiles
	p.IgnoredOtherFiles = pp.IgnoredOtherFiles
	p.CFiles = pp.CFiles
	p.CXXFiles = pp.CXXFiles
	p.MFiles = pp.MFiles
	p.HFiles = pp.HFiles
	p.FFiles = pp.FFiles
	p.SFiles = pp.SFiles
	p.SwigFiles = pp.SwigFiles
	p.SwigCXXFiles = pp.SwigCXXFiles
	p.SysoFiles = pp.SysoFiles
	p.CgoCFLAGS = pp.CgoCFLAGS
	p.CgoCPPFLAGS = pp.CgoCPPFLAGS
	p.CgoCXXFLAGS = pp.CgoCXXFLAGS
	p.CgoFFLAGS = pp.CgoFFLAGS
	p.CgoLDFLAGS = pp.CgoLDFLAGS
	p.CgoPkgConfig = pp.CgoPkgConfig
	// 我们修改了p.导入，所以现在复制。
	p.Imports = make([]string, len(pp.Imports))
	copy(p.Imports, pp.Imports)
	p.Internal.RawImports = pp.Imports
	p.TestGoFiles = pp.TestGoFiles
	p.TestImports = pp.TestImports
	p.XTestGoFiles = pp.XTestGoFiles
	p.XTestImports = pp.XTestImports
	if opts.IgnoreImports {
		p.Imports = nil
		p.Internal.RawImports = nil
		p.TestImports = nil
		p.XTestImports = nil
	}
	p.EmbedPatterns = pp.EmbedPatterns
	p.TestEmbedPatterns = pp.TestEmbedPatterns
	p.XTestEmbedPatterns = pp.XTestEmbedPatterns
	p.Internal.OrigImportPath = pp.ImportPath
}

// 程序包错误描述了加载有关程序包的信息时发生的错误。
type PackageError struct {
	ImportStack      []string // 从命令行上命名的包到这个包的最短路径
	Pos              string   // 错误位置
	Err              error    // 错误本身
	IsImportCycle    bool     // 错误是一个导入周期
	Hard             bool     // 错误是软的还是硬的；软错误在某些地方被忽略
	alwaysPrintStack bool     // 是否总是打印ImportStack 
}

func (p *PackageError) Error() string {
	// TODO（#43696）：根据
	// 错误类型和包是否在主模块中决定何时打印堆栈或位置。
	// 记录理由。
	if p.Pos != "" && (len(p.ImportStack) == 0 || !p.alwaysPrintStack) {
		// 忽略导入堆栈。文件的完整路径，其中错误
		// 是最重要的。
		return p.Pos + ": " + p.Err.Error()
	}

	// 如果该错误是一个导入错误，并且堆栈上的最后一条路径出现在错误消息中，请从堆栈中忽略该路径以避免重复。
	// 如果一个导入处理器包装了另一个与堆栈上最后一条路径匹配的导入处理器，我们不会忽略该路径。如果省略“imports B”，像
	// “package A imports B:由B导致的错误加载C”这样的错误将不会更清楚。
	if len(p.ImportStack) == 0 {
		return p.Err.Error()
	}
	var optpos string
	if p.Pos != "" {
		optpos = "\n\t" + p.Pos
	}
	return "package " + strings.Join(p.ImportStack, "\n\timports ") + optpos + ": " + p.Err.Error()
}

func (p *PackageError) Unwrap() error { return p.Err }

// PackageError实现了MarshallJSON，因此Err被封送为字符串
// 并且省略了非必需字段。
func (p *PackageError) MarshalJSON() ([]byte, error) {
	perr := struct {
		ImportStack []string
		Pos         string
		Err         string
	}{p.ImportStack, p.Pos, p.Err.Error()}
	return json.Marshal(perr)
}

func (p *PackageError) setPos(posList []token.Position) {
	if len(posList) == 0 {
		return
	}
	pos := posList[0]
	pos.Filename = base.ShortPath(pos.Filename)
	p.Pos = pos.String()
}

// 导入密码错误是一种错误类型，它会阻止为给定的导入路径加载包。加载这样一个包时，*包是
// 返回的，并带有错误包装导入包。错误：错误附加到
// 导入的包，而不是导入的包。
// 
// 导入路径返回的字符串必须出现在
// 返回的字符串中。错误。包装ImportPath错误的错误（例如PackageError）可能会忽略
// 导入路径。
type ImportPathError interface {
	error
	ImportPath() string
}

var (
	_ ImportPathError = (*importError)(nil)
	_ ImportPathError = (*mainPackageError)(nil)
	_ ImportPathError = (*modload.ImportMissingError)(nil)
	_ ImportPathError = (*modload.ImportMissingSumError)(nil)
	_ ImportPathError = (*modload.DirectImportFromImplicitDependencyError)(nil)
)

type importError struct {
	importPath string
	err        error // 使用fmt创建。Errorf 
}

func ImportErrorf(path, format string, args ...any) ImportPathError {
	err := &importError{importPath: path, err: fmt.Errorf(format, args...)}
	if errStr := err.Error(); !strings.Contains(errStr, path) {
		panic(fmt.Sprintf("path %q not in error %q", path, errStr))
	}
	return err
}

func (e *importError) Error() string {
	return e.err.Error()
}

func (e *importError) Unwrap() error {
	// 不要直接返回e.err，因为只有当%w 
	// 被传递给ImportErrorf时，我们才包装一个错误。huang jian defg
	return errors.Unwrap(e.err)
}

func (e *importError) ImportPath() string {
	return e.importPath
}

// 测试包的导入路径是对应
// 添加后缀“_test”的非测试包的导入路径。
type ImportStack []string

func (s *ImportStack) Push(p string) {
	*s = append(*s, p)
}

func (s *ImportStack) Pop() {
	*s = (*s)[0 : len(*s)-1]
}

func (s *ImportStack) Copy() []string {
	return append([]string{}, *s...)
}

func (s *ImportStack) Top() string {
	if len(*s) == 0 {
		return ""
	}
	return (*s)[len(*s)-1]
}

// 短于报告sp是否比t短。
// 我们使用它来记录导致特定包的最短导入序列
// 。
func (sp *ImportStack) shorterThan(t []string) bool {
	s := *sp
	if len(s) != len(t) {
		return len(s) < len(t)
	}
	// 如果长度相同，则使用字符串排序来解决关系。
	for i := range s {
		if s[i] != t[i] {
			return s[i] < t[i]
		}
	}
	return false // 它们相等
}

// packageCache是LoadImport的查找缓存，
// 因此，如果我们多次查找包
// 我们每次都返回相同的指针。
var packageCache = map[string]*Package{}

// ClearPackageCache清除内存中的包缓存和预加载缓存。
// 只供基于GOPATH的“go get”使用。
// TODO（jayconrod）：当基于GOPATH的“go get”被删除时，删除此函数。
func ClearPackageCache() {
	for name := range packageCache {
		delete(packageCache, name)
	}
	resolvedImportCache.Clear()
	packageDataCache.Clear()
}

// ClearPackageCachePartial从
// 内存包缓存和预加载缓存中清除具有给定导入路径的包。只供
// GOPATH-based“go-get”使用。
// TODO（jayconrod）：当基于GOPATH的“go get”被删除时，删除此函数。
func ClearPackageCachePartial(args []string) {
	shouldDelete := make(map[string]bool)
	for _, arg := range args {
		shouldDelete[arg] = true
		if p := packageCache[arg]; p != nil {
			delete(packageCache, arg)
		}
	}
	resolvedImportCache.DeleteIf(func(key any) bool {
		return shouldDelete[key.(importSpec).path]
	})
	packageDataCache.DeleteIf(func(key any) bool {
		return shouldDelete[key.(string)]
	})
}

// 重新加载PackageNoFlags类似于LoadImport，但确保
// 不使用包缓存。
// 只供基于GOPATH的“go get”使用。
// TODO（rsc）：当基于GOPATH的“go get”被删除时，删除此函数。
func ReloadPackageNoFlags(arg string, stk *ImportStack) *Package {
	p := packageCache[arg]
	if p != nil {
		delete(packageCache, arg)
		resolvedImportCache.DeleteIf(func(key any) bool {
			return key.(importSpec).path == p.ImportPath
		})
		packageDataCache.Delete(p.ImportPath)
	}
	return LoadImport(context.TODO(), PackageOpts{}, arg, base.Cwd(), nil, stk, nil, 0)
}

// dirToImportPath返回用于包的伪导入路径
// Go路径之外。它以/开头，然后包含指向目录的完整路径
// 。如果包位于c:\home\gopher\my\pkg中，则
// 伪导入路径为u/c_u/home/gopher/my/pkg。
// 使用这样的伪导入路径会使导入不再是
// 的特例，因此所有处理普通导入的代码
// 都会自动工作。
func dirToImportPath(dir string) string {
	return pathpkg.Join("_", strings.Map(makeImportValid, filepath.ToSlash(dir)))
}

func makeImportValid(r rune) rune {
	// 应该与Go规范、编译器和../匹配/go/parser/parser。go:/isValidImport。
	const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD"
	if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) {
		return '_'
	}
	return r
}

// 加载导入和下载的模式标志（在get.go中）。
const (
	// ResolveImport意味着loadImport应该进行导入路径扩展。
	// 也就是说，ResolveImport意味着导入路径来自
	// 一个源文件，尚未扩展到
	// 出售或可能的模块调整。
	// 每个导入路径最初都应该使用ResolveImport、
	// 加载，然后扩展版本（例如其中的/vendor/
	// 被记录为规范导入路径。此时，该包的未来加载
	// 不能通过ResolveImport，因为
	// disallowVendor将拒绝直接使用包含/vendor/的路径。
	ResolveImport = 1 << iota

	// ResolveModule供下载（“go get”的一部分），并表示
	// 应进行模块调整，但不应进行供应商调整。
	ResolveModule

	// GetTestDeps供下载（“go get”的一部分），并表示也应获取测试依赖项。
	GetTestDeps
)

// LoadImport扫描以路径命名的目录，该路径必须是导入路径
// 但可能是本地导入路径（绝对文件系统路径或以/或../开头的
// 路径）。本地相对路径是相对于srcDir解释的。
// 它返回一个*包，描述在该目录中找到的包。
// LoadImport不设置工具标志，只能由
// 作为更大加载操作的一部分，以及基于GOPATH的“go get”使用。
// TODO（rsc）：当基于GOPATH的“go get”被删除时，取消导出此函数。
func LoadImport(ctx context.Context, opts PackageOpts, path, srcDir string, parent *Package, stk *ImportStack, importPos []token.Position, mode int) *Package {
	return loadImport(ctx, opts, nil, path, srcDir, parent, stk, importPos, mode)
}

func loadImport(ctx context.Context, opts PackageOpts, pre *preload, path, srcDir string, parent *Package, stk *ImportStack, importPos []token.Position, mode int) *Package {
	if path == "" {
		panic("LoadImport called with empty package path")
	}

	var parentPath, parentRoot string
	parentIsStd := false
	if parent != nil {
		parentPath = parent.ImportPath
		parentRoot = parent.Root
		parentIsStd = parent.Standard
	}
	bp, loaded, err := loadPackageData(ctx, path, parentPath, srcDir, parentRoot, parentIsStd, mode)
	if loaded && pre != nil && !opts.IgnoreImports {
		pre.preloadImports(ctx, opts, bp.Imports, bp)
	}
	if bp == nil {
		p := &Package{
			PackagePublic: PackagePublic{
				ImportPath: path,
				Incomplete: true,
			},
		}
		if importErr, ok := err.(ImportPathError); !ok || importErr.ImportPath() != path {
			// 如果错误中还没有路径，则只将路径添加到错误的导入堆栈中。
			// 
			// TODO（bcmills）：setLoadPackageDataError本身有一个类似的Push/Pop 
			// 序列，在经验上不会触发这些错误，由
			// 一个有点复杂的条件保护。找出如何推广
			// 条件并消除这里的显式调用。
			stk.Push(path)
			defer stk.Pop()
		}
		p.setLoadPackageDataError(err, path, stk, nil)
		return p
	}

	importPath := bp.ImportPath
	p := packageCache[importPath]
	if p != nil {
		stk.Push(path)
		p = reusePackage(p, stk)
		stk.Pop()
	} else {
		p = new(Package)
		p.Internal.Local = build.IsLocalImport(path)
		p.ImportPath = importPath
		packageCache[importPath] = p

		// 加载包。
		// loadPackageData可能返回bp！=nil即使发生错误，
		// 以返回部分信息。
		p.load(ctx, opts, path, stk, importPos, bp, err)

		if !cfg.ModulesEnabled && path != cleanImport(path) {
			p.Error = &PackageError{
				ImportStack: stk.Copy(),
				Err:         ImportErrorf(path, "non-canonical import path %q: should be %q", path, pathpkg.Clean(path)),
			}
			p.Incomplete = true
			p.Error.setPos(importPos)
		}
	}

	// 每次导入时都会检查，因为规则取决于执行导入的代码。
	if perr := disallowInternal(ctx, srcDir, parent, parentPath, p, stk); perr != p {
		perr.Error.setPos(importPos)
		return perr
	}
	if mode&ResolveImport != 0 {
		if perr := disallowVendor(srcDir, path, parentPath, p, stk); perr != p {
			perr.Error.setPos(importPos)
			return perr
		}
	}

	if p.Name == "main" && parent != nil && parent.Dir != p.Dir {
		perr := *p
		perr.Error = &PackageError{
			ImportStack: stk.Copy(),
			Err:         ImportErrorf(path, "import %q is a program, not an importable package", path),
		}
		perr.Error.setPos(importPos)
		return &perr
	}

	if p.Internal.Local && parent != nil && !parent.Internal.Local {
		perr := *p
		var err error
		if path == "." {
			err = ImportErrorf(path, "%s: cannot import current directory", path)
		} else {
			err = ImportErrorf(path, "local import %q in non-local package", path)
		}
		perr.Error = &PackageError{
			ImportStack: stk.Copy(),
			Err:         err,
		}
		perr.Error.setPos(importPos)
		return &perr
	}

	return p
}

// loadPackageData加载构造*包所需的信息。结果
// 被缓存，之后对同一个包的loadPackageData的调用将返回相同的数据。
// 
// loadPackageData返回非零包，即使错误为非零，除非
// 包路径格式不正确（例如，路径包含“mod/”或“@”）。
// 
// loadPackageData返回一个布尔值loaded，如果这是第一次加载包时的
// 则为真。在这种情况下，呼叫者可以预加载导入。
func loadPackageData(ctx context.Context, path, parentPath, parentDir, parentRoot string, parentIsStd bool, mode int) (bp *build.Package, loaded bool, err error) {
	if path == "" {
		panic("loadPackageData called with empty package path")
	}

	if strings.HasPrefix(path, "mod/") {
		// 以“mod/”开头的路径可能会意外
		// 查看$GOPATH/pkg/mod/中的模块缓存目录树。
		// 这个前缀归Go core所有，可能在
		// 标准库中使用（因为它不是以域名开头），所以完全不允许。
		return nil, false, fmt.Errorf("disallowed import path %q", path)
	}

	if strings.Contains(path, "@") {
		return nil, false, errors.New("can only use path@version syntax with 'go get' and 'go install' in module-aware mode")
	}

	// 确定规范的包路径和目录。
	// 对于本地导入，标识符是伪导入路径
	// 我们从完整目录创建到包。
	// 否则它是通常的导入路径。
	// 对于卖方进口，它是扩展形式。
	// 
	// 请注意，当启用模块时，本地导入路径通常由modload规范化。以前是装包裹的。但是，如果解析本地路径时出现
	// 错误，将返回未经转换的
	// 以便“go list-e”报告有用的内容。
	importKey := importSpec{
		path:        path,
		parentPath:  parentPath,
		parentDir:   parentDir,
		parentRoot:  parentRoot,
		parentIsStd: parentIsStd,
		mode:        mode,
	}
	r := resolvedImportCache.Do(importKey, func() any {
		var r resolvedImport
		if cfg.ModulesEnabled {
			r.dir, r.path, r.err = modload.Lookup(parentPath, parentIsStd, path)
		} else if build.IsLocalImport(path) {
			r.dir = filepath.Join(parentDir, path)
			r.path = dirToImportPath(r.dir)
		} else if mode&ResolveImport != 0 {
			// 我们自己做路径解析，因为我们想找到在packageCache中使用的密钥，而不需要
			// 重复调用buildContext的开销。进口
			// 其他一些地方也需要该代码。
			r.path = resolveImportPath(path, parentPath, parentDir, parentRoot, parentIsStd)
		} else if mode&ResolveModule != 0 {
			r.path = moduleImportPath(path, parentPath, parentDir, parentRoot)
		}
		if r.path == "" {
			r.path = path
		}
		return r
	}).(resolvedImport)
	// 不变量：r.path设置为解析的导入路径。如果无法解析路径
	// 则将r.path设置为源导入路径path。
	// r.path永远不会为空。

	// 从其目录加载包。如果我们在解析包的导入路径时已经找到了包的
	// 目录，请使用该目录。
	data := packageDataCache.Do(r.path, func() any {
		loaded = true
		var data packageData
		if r.dir != "" {
			var buildMode build.ImportMode
			if !cfg.ModulesEnabled {
				buildMode = build.ImportComment
			}
			data.p, data.err = cfg.BuildContext.ImportDir(r.dir, buildMode)
			if cfg.ModulesEnabled {
				// 覆盖数据。p、 Root，因为ImportDir将其设置为$GOPATH，如果
				// 模块位于$GOPATH/src内。
				if info := modload.PackageModuleInfo(ctx, path); info != nil {
					data.p.Root = info.Dir
				}
			}
			if r.err != nil {
				if data.err != nil {
					// ImportDir给了我们一个错误，模块加载器给了我们另一个错误。
					// 我们任意选择从ImportDir中保留错误，因为
					// 这是我们的测试已经期望的，在大多数情况下，它似乎提供了一点
					// 更详细的信息。
				} else if errors.Is(r.err, imports.ErrNoGo) {
					// ImportDir说包中有文件，但模块
					// loader说没有。哪一个是正确的？
					// 如果没有这个特殊的案例攻击，TestScript/test_vet案例将在vetfail/p1包（添加到CL 83955中）上失败。
					// 显然是进口。ShouldBuild偏向于拒绝具有无效生成约束的文件
					// 而ImportDir偏向于接受它们。
					// 
					// TODO（#41410：弄清楚这实际上应该如何工作，并修复这个混乱。
					// 不是Vendor，或者我们已经找到了Vendor路径。
				} else {
					data.err = r.err
				}
			}
		} else if r.err != nil {
			data.p = new(build.Package)
			data.err = r.err
		} else if cfg.ModulesEnabled && path != "unsafe" {
			data.p = new(build.Package)
			data.err = fmt.Errorf("unknown import path %q: internal error: module loader did not resolve import", r.path)
		} else {
			buildMode := build.ImportComment
			if mode&ResolveImport == 0 || r.path != path {
				buildMode |= build.IgnoreVendor
			}
			data.p, data.err = cfg.BuildContext.Import(r.path, parentDir, buildMode)
		}
		data.p.ImportPath = r.path

		// 在go/build.Context.Import 
		// 可能会给我们一个我们不想要的路径的情况下设置data.p.BinDir。
		if !data.p.Goroot {
			if cfg.GOBIN != "" {
				data.p.BinDir = cfg.GOBIN
			} else if cfg.ModulesEnabled {
				data.p.BinDir = modload.BinDir()
			}
		}

		if !cfg.ModulesEnabled && data.err == nil &&
			data.p.ImportComment != "" && data.p.ImportComment != path &&
			!strings.Contains(path, "/vendor/") && !strings.HasPrefix(path, "vendor/") {
			data.err = fmt.Errorf("code in directory %s expects import %q", data.p.Dir, data.p.ImportComment)
		}
		return data
	}).(packageData)

	return data.p, loaded, data.err
}

// importSpec在源代码中描述了一个导入声明。它被用作
// resolvedImportCache的缓存密钥。
type importSpec struct {
	path                              string
	parentPath, parentDir, parentRoot string
	parentIsStd                       bool
	mode                              int
}

// resolvedImport保存包的规范标识符。它还可能包含
// 程序包目录的路径，如果出现错误，则包含一个错误。resolvedImport 
// 是resolvedImportCache中的值类型。
type resolvedImport struct {
	path, dir string
	err       error
}

// packageData保存从包中加载的信息。它是packageDataCache中的值类型
// 。
type packageData struct {
	p   *build.Package
	err error
}

// resolvedImportCache将导入字符串（importSpec）映射到规范的包名
// （resolvedImport）。
var resolvedImportCache par.Cache

// packageDataCache将规范包名称（字符串）映射到包元数据
// （packageData）。
var packageDataCache par.Cache

// PrelowWorkerCount是可以加载
// 包的并发Goroutine数。从实验上看，超过
// 4名员工的回报率呈递减趋势。这是在以下机器上测量的。
// 
// /*配备4核Intel core i7 CPU的MacBookPro 
// /*配备6核Intel Xeon CPU的Linux工作站
// /*配备24核Intel Xeon CPU的Linux工作站
// 
// 该工作负载很可能（尽管尚未确认）受到内存带宽的限制。我们没有一个好的方法来确定
// 工作人员的数量，这将使总线饱和，所以运行时。GOMAXPROCS 
// 似乎是合理的违约。
var preloadWorkerCount = runtime.GOMAXPROCS(0)

// preload保存用于管理包数据的并发预加载的状态。
// 
// 在加载大型
// 包图之前，应使用newPreload创建预加载。包加载完成时必须调用flush 
// 以确保预加载goroutines不再处于活动状态。这是必要的
// 因为不能安全地读写全局可变状态
// 。特别是，packageDataCache可以通过GOPATH模式下的“go get”
// 和modload清除。加载（通过modload.Lookup访问）可能是
// 由modload修改。加载包。
type preload struct {
	cancel chan struct{}
	sema   chan struct{}
}

// newPreload创建一个新的预加载程序。以后必须调用flush，以避免修改时访问全局状态。
func newPreload() *preload {
	pre := &preload{
		cancel: make(chan struct{}),
		sema:   make(chan struct{}, preloadWorkerCount),
	}
	return pre
}

// 预加载模式匹配的包路径的数据。
// 当preloadMatches返回时，某些包可能尚未加载，但
// loadPackageData和loadImport始终可以安全调用。
func (pre *preload) preloadMatches(ctx context.Context, opts PackageOpts, matches []*search.Match) {
	for _, m := range matches {
		for _, pkg := range m.Pkgs {
			select {
			case <-pre.cancel:
				return
			case pre.sema <- struct{}{}:
				go func(pkg string) {
					mode := 0 // 不要使用自动售货机或模块导入解析
					bp, loaded, err := loadPackageData(ctx, pkg, "", base.Cwd(), "", false, mode)
					<-pre.sema
					if bp != nil && loaded && err == nil && !opts.IgnoreImports {
						pre.preloadImports(ctx, opts, bp.Imports, bp)
					}
				}(pkg)
			}
		}
	}
}

// 预加载导入将要预加载的导入列表排入队列。
// 当preloadImports返回时，某些包可能尚未加载，
// 但是loadPackageData和loadImport始终可以安全调用。
func (pre *preload) preloadImports(ctx context.Context, opts PackageOpts, imports []string, parent *build.Package) {
	parentIsStd := parent.Goroot && parent.ImportPath != "" && search.IsStandardImportPath(parent.ImportPath)
	for _, path := range imports {
		if path == "C" || path == "unsafe" {
			continue
		}
		select {
		case <-pre.cancel:
			return
		case pre.sema <- struct{}{}:
			go func(path string) {
				bp, loaded, err := loadPackageData(ctx, path, parent.ImportPath, parent.Dir, parent.Root, parentIsStd, ResolveImport)
				<-pre.sema
				if bp != nil && loaded && err == nil && !opts.IgnoreImports {
					pre.preloadImports(ctx, opts, bp.Imports, bp)
				}
			}(path)
		}
	}
}

// 冲洗停止，等待预加载操作。刷新块，直到对
// loadPackageData的预加载调用完成。预加载程序不会再调用
// 来加载PackageData。
func (pre *preload) flush() {
	// 刷新通常被推迟。
	// 不要挂起程序等待工人恐慌。
	if v := recover(); v != nil {
		panic(v)
	}

	close(pre.cancel)
	for i := 0; i < preloadWorkerCount; i++ {
		pre.sema <- struct{}{}
	}
}

func cleanImport(path string) string {
	orig := path
	path = pathpkg.Clean(path)
	if strings.HasPrefix(orig, "./") && path != ".." && !strings.HasPrefix(path, "../") {
		path = "./" + path
	}
	return path
}

var isDirCache par.Cache

func isDir(path string) bool {
	return isDirCache.Do(path, func() any {
		fi, err := fsys.Stat(path)
		return err == nil && fi.IsDir()
	}).(bool)
}

// ResolveImportPath在父级中出现时返回路径的真正含义。
// 使用两种不同的分辨率。
// 首先是Go 1.5自动售货机（golang.org/s/go15vendor）。
// 如果未触发供应商扩展，则路径也受
// Go 1.11模块遗留转换的影响（golang.org/issue/25069）。
func ResolveImportPath(parent *Package, path string) (found string) {
	var parentPath, parentDir, parentRoot string
	parentIsStd := false
	if parent != nil {
		parentPath = parent.ImportPath
		parentDir = parent.Dir
		parentRoot = parent.Root
		parentIsStd = parent.Standard
	}
	return resolveImportPath(path, parentPath, parentDir, parentRoot, parentIsStd)
}

func resolveImportPath(path, parentPath, parentDir, parentRoot string, parentIsStd bool) (found string) {
	if cfg.ModulesEnabled {
		if _, p, e := modload.Lookup(parentPath, parentIsStd, path); e == nil {
			return p
		}
		return path
	}
	found = vendoredImportPath(path, parentPath, parentDir, parentRoot)
	if found != path {
		return found
	}
	return moduleImportPath(path, parentPath, parentDir, parentRoot)
}

// dirAndRoot返回包p的源目录和工作区根
// ，保证root是dir的路径前缀。
func dirAndRoot(path string, dir, root string) (string, string) {
	origDir, origRoot := dir, root
	dir = filepath.Clean(dir)
	root = filepath.Join(root, "src")
	if !str.HasFilePathPrefix(dir, root) || path != "command-line-arguments" && filepath.Join(root, path) != dir {
		// 在报告错误之前查找符号链接。
		dir = expandPath(dir)
		root = expandPath(root)
	}

	if !str.HasFilePathPrefix(dir, root) || len(dir) <= len(root) || dir[len(root)] != filepath.Separator || path != "command-line-arguments" && !build.IsLocalImport(path) && filepath.Join(root, path) != dir {
		debug.PrintStack()
		base.Fatalf("unexpected directory layout:\n"+
			"	import path: %s\n"+
			"	root: %s\n"+
			"	dir: %s\n"+
			"	expand root: %s\n"+
			"	expand dir: %s\n"+
			"	separator: %s",
			path,
			filepath.Join(origRoot, "src"),
			filepath.Clean(origDir),
			origRoot,
			origDir,
			string(filepath.Separator))
	}

	return dir, root
}

// vendoredImportPath在父级中出现时返回路径的供应商扩展。
// 如果父级是x/y/z，则路径可能会扩展到x/y/z/vendor/path、x/y/vendor/path、
// x/vendor/path、vendor/path，或者如果它们都不存在，则保留路径。
// vendoredImportPath返回扩展路径，如果未找到扩展，则返回原始路径。
func vendoredImportPath(path, parentPath, parentDir, parentRoot string) (found string) {
	if parentRoot == "" {
		return path
	}

	dir, root := dirAndRoot(parentPath, parentDir, parentRoot)

	vpath := "vendor/" + path
	for i := len(dir); i >= len(root); i-- {
		if i < len(dir) && dir[i] != filepath.Separator {
			continue
		}
		// 注意：在检查
		// 供应商/路径目录之前检查供应商目录有助于我们更频繁地访问
		// isDir缓存。它还帮助我们准备一份更有用的
		// 我们查找的位置列表，以便在找不到导入时报告。
		if !isDir(filepath.Join(dir[:i], "vendor")) {
			continue
		}
		targ := filepath.Join(dir[:i], vpath)
		if isDir(targ) && hasGoFiles(targ) {
			importPath := parentPath
			if importPath == "command-line-arguments" {
				// 如果是父母。ImportPath是“命令行参数”。
				// 设置为根目录的相对目录（也是切碎的根目录）
				importPath = dir[len(root)+1:]
			}
			// 我们从父目录c:\gopath\src\foo\bar\baz\qux\xyzy开始。
			// 我们知道父目录的导入路径。
			// 我们删除了一些path元素，并添加了vendor\path以生成c:\gopath\src\foo\bar\baz\vendor\path。
			// 现在我们想知道该目录的导入路径。
			// 通过从父级的导入路径
			// （实际上是相同数量的字节），然后追加/vendor/path来构造它。我们从c:\gopath\src\foo\bar走到c:\gopath\src\vendor\path。
			// 中切掉相同数量的路径元素
			chopped := len(dir) - i
			if chopped == len(importPath)+1 {
				// 我们切碎了\foo\bar（长度8），但导入路径是“foo/bar”（长度7）。
				// 使用不带任何前缀的“供应商/路径”。
				return vpath
			}
			return importPath[:len(importPath)-chopped] + "/" + vpath
		}
	}
	return path
}

var (
	modulePrefix   = []byte("\nmodule ")
	goModPathCache par.Cache
)

// goModPath返回go中的模块路径。mod in dir，如果有的话。
func goModPath(dir string) (path string) {
	return goModPathCache.Do(dir, func() any {
		data, err := os.ReadFile(filepath.Join(dir, "go.mod"))
		if err != nil {
			return ""
		}
		var i int
		if bytes.HasPrefix(data, modulePrefix[1:]) {
			i = 0
		} else {
			i = bytes.Index(data, modulePrefix)
			if i < 0 {
				return ""
			}
			i++
		}
		line := data[i:]

		// 在\n处剪切线，如果存在，则删除尾随线。
		if j := bytes.IndexByte(line, '\n'); j >= 0 {
			line = line[:j]
		}
		if line[len(line)-1] == '\r' {
			line = line[:len(line)-1]
		}
		line = line[len("module "):]

		// 如果引用，请取消引用。
		path = strings.TrimSpace(string(line))
		if path != "" && path[0] == '"' {
			s, err := strconv.Unquote(path)
			if err != nil {
				return ""
			}
			path = s
		}
		return path
	}).(string)
}

// findVersionElement返回路径中最终版本元素/vN的切片索引。
// 如果没有这样的元素，则返回-1，-1。
func findVersionElement(path string) (i, j int) {
	j = len(path)
	for i = len(path) - 1; i >= 0; i-- {
		if path[i] == '/' {
			if isVersionElement(path[i+1 : j]) {
				return i, j
			}
			j = i
		}
	}
	return -1, -1
}

// isVersionElement报告s是否是格式良好的路径版本元素：
// v2、v3、v10等，但不报告v0、v05、v1。
func isVersionElement(s string) bool {
	if len(s) < 2 || s[0] != 'v' || s[1] == '0' || s[1] == '1' && len(s) == 2 {
		return false
	}
	for i := 1; i < len(s); i++ {
		if s[i] < '0' || '9' < s[i] {
			return false
		}
	}
	return true
}

// moduleImportPath将go模块中找到的导入路径转换回普通版本中可以解析的路径。
// 
// 将“新”代码定义为带go的代码。mod file位于同一目录
// 或父目录中。如果新代码中的导入显示x/y/v2/z，但
// x/y/v2/z不存在且x/y/go。mod表示“module x/y/v2”，
// 然后go build将把导入读取为x/y/z。
// 见golang。org/issue/25069。
func moduleImportPath(path, parentPath, parentDir, parentRoot string) (found string) {
	if parentRoot == "" {
		return path
	}

	// 如果路径中没有vN元素，请不要使用它。
	// /（下面的代码也会这样做，但只在
	// 其他一些文件系统访问，我们可以通过提前返回来避免
	// 这里。）ABCFDG＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊〕
	if i, _ := findVersionElement(path); i < 0 {
		return path
	}

	dir, root := dirAndRoot(parentPath, parentDir, parentRoot)

	for i := len(dir); i >= len(root); i-- {
		if i < len(dir) && dir[i] != filepath.Separator {
			continue
		}
		if goModPath(dir[:i]) != "" {
			goto HaveGoMod
		}
	}
	// 这段代码不在一棵树中。mod，
	// 所以不要对路径进行更改。
	return path

HaveGoMod:
	// 此导入位于一棵树中，正在执行。摩登派青年
	// 允许它将GOPATH/src/x/y/z中的代码引用为x/y/v2/z 
	// 如果GOPATH/src/x/y/go。mod表示模块“x/y/v2”，

	// 如果存在x/y/v2/z，则未经修改就使用它。
	if bp, _ := cfg.BuildContext.Import(path, "", build.IgnoreVendor); bp.Dir != "" {
		return path
	}

	// 否则，请尝试一下。mod提供一个版本元素。
	// 某些类似版本的元素可能出现在路径中，但不是
	// 是模块版本；我们跳过这些来寻找模块
	// 版本。例如，模块m/v2可能有一个
	// 包m/v2/api/v1/foo。
	limit := len(path)
	for limit > 0 {
		i, j := findVersionElement(path[:limit])
		if i < 0 {
			return path
		}
		if bp, _ := cfg.BuildContext.Import(path[:i], "", build.IgnoreVendor); bp.Dir != "" {
			if mpath := goModPath(bp.Dir); mpath != "" {
				// 找到了一个有效的go。mod文件，所以我们停止搜索。
				// 如果路径是m/v2/p，我们找到了m/go。mod表示
				// /“模块m/v2”，然后我们返回“m/p”。
				if mpath == path[:j] {
					return path[:i] + path[j:]
				}
				// 否则返回原始路径。我们没有发现任何值得重写的东西，还有go。MOD指示我们应该ABCDEG
				return path
			}
		}
		limit = i
	}
	return path
}

// hasGoFiles报告dir是否包含名称以结尾的文件。去
// 对于供应商检查，我们必须排除不包含。去查档案。
// 否则，不可能只提供a/b/c，而仍然导入
// 非提供a/b。参见golang。org/issue/13832。
func hasGoFiles(dir string) bool {
	files, _ := os.ReadDir(dir)
	for _, f := range files {
		if !f.IsDir() && strings.HasSuffix(f.Name(), ".go") {
			return true
		}
	}
	return false
}

// reusePackage重用包p以满足导入堆栈stk顶部的导入。如果此使用导致导入循环，
// reusePackage将更新p的错误信息以记录循环。
func reusePackage(p *Package, stk *ImportStack) *Package {
	// 我们使用p.Internal。Imports==nil以检测
	// 在其自己的loadPackage调用
	// （以下所有递归都发生在设置p.Internal.Imports之前）中的包。
	if p.Internal.Imports == nil {
		if p.Error == nil {
			p.Error = &PackageError{
				ImportStack:   stk.Copy(),
				Err:           errors.New("import cycle not allowed"),
				IsImportCycle: true,
			}
		} else if !p.Error.IsImportCycle {
			// 如果已经设置了错误，但它并不表示
			// 我们正处于导入周期，请设置IsImportCycle，这样我们就不会因为
			// 而陷入一个循环。
			p.Error.IsImportCycle = true
		}
		p.Incomplete = true
	}
	// 如果有导入周期，请不要在错误中重写导入堆栈。
	// 如果我们这样做，我们将失去描述周期的路径。
	if p.Error != nil && !p.Error.IsImportCycle && stk.shorterThan(p.Error.ImportStack) {
		p.Error.ImportStack = stk.Copy()
	}
	return p
}

// Disallowernal检查srcDir（包含程序包导入路径，如果非空）
// 是否允许导入p。
// 如果允许导入，Disallowernal将返回原始程序包p。
// 如果不允许，将返回一个只包含适当错误的新程序包。
func disallowInternal(ctx context.Context, srcDir string, importer *Package, importerPath string, p *Package, stk *ImportStack) *Package {
	// golang。org/s/go14internal:
	// 如果导入代码不在以“internal”目录的父目录为根的树
	// 中，则不允许导入包含元素“internal”的路径。

	// 加载包时出错；停在这里。
	if p.Error != nil {
		return p
	}

	// 生成的“testmain”包被允许访问testing/internal/。。。，
	// 好像它被生成到测试目录树
	// （它实际上位于任何Go树之外的临时目录中）。
	// 这清除了以前在将功能传递给测试包时遇到的麻烦。
	if str.HasPathPrefix(p.ImportPath, "testing/internal") && importerPath == "testmain" {
		return p
	}

	// 我们不能用gccgo检查标准包装。
	if cfg.BuildContext.Compiler == "gccgo" && p.Standard {
		return p
	}

	// 排序包依赖于内部/reflectlite，但在引导过程中
	// 路径重写会导致正常的内部检查失败。
	// 相反，在引导过程中忽略内部规则。
	if p.Standard && strings.HasPrefix(importerPath, "bootstrap/") {
		return p
	}

	// 导入路径为空：我们使用命令行中给定的名称启动了
	// 导入，而不是
	// 导入。命令行上列出的任何内容都可以。
	if importerPath == "" {
		return p
	}

	// 检查“内部”元素：三种情况取决于字符串的开头和/或结尾。
	i, ok := findInternal(p.ImportPath)
	if !ok {
		return p
	}

	// 内部存在。
	// 将导入路径映射回内部文件父级对应的目录。
	if i > 0 {
		i-- // 在“../internal”
	}

	if p.Module == nil {
		parent := p.Dir[:i+len(p.Dir)-len(p.ImportPath)]

		if str.HasFilePathPrefix(filepath.Clean(srcDir), filepath.Clean(parent)) {
			return p
		}

		// 报告错误之前，请查找符号链接。
		srcDir = expandPath(srcDir)
		parent = expandPath(parent)
		if str.HasFilePathPrefix(filepath.Clean(srcDir), filepath.Clean(parent)) {
			return p
		}
	} else {
		// p在一个模块中，因此，请根据导入程序的导入路径而不是文件路径的
		// 使其可用（https:
		if importer.Internal.CmdlineFiles {
			// 导入器是一个命令行文件列表。
			// 假设导入路径是包含这些文件的
			// 目录的导入路径。
			// 如果目录在主模块之外，这将解析为“，
			// 不是任何有效模块的前缀。
			importerPath, _ = modload.MainModules.DirImportPath(ctx, importer.Dir)
		}
		parentOfInternal := p.ImportPath[:i]
		if str.HasPathPrefix(importerPath, parentOfInternal) {
			return p
		}
	}

	// 存在Internal，srcDir在父级树之外。不允许。
	perr := *p
	perr.Error = &PackageError{
		alwaysPrintStack: true,
		ImportStack:      stk.Copy(),
		Err:              ImportErrorf(p.ImportPath, "use of internal package "+p.ImportPath+" not allowed"),
	}
	perr.Incomplete = true
	return &perr
}

// findInternal在给定的导入路径中查找最终的“内部”路径元素。
// 如果没有，findInternal返回ok=false。
// 否则，findInternal返回ok=true和“internal”的索引。
func findInternal(path string) (index int, ok bool) {
	// 三种情况，取决于字符串开头/结尾是否有内部。
	// 顺序很重要：我们必须返回最终元素的索引
	// 因为最终元素对进口商的要求最严格。
	switch {
	case strings.HasSuffix(path, "/internal"):
		return len(path) - len("internal"), true
	case strings.Contains(path, "/internal/"):
		return strings.LastIndex(path, "/internal/") + 1, true
	case path == "internal", strings.HasPrefix(path, "internal/"):
		return 0, true
	}
	return 0, false
}

// 不允许供应商检查是否允许srcDir将p作为路径导入。
// 如果允许导入，则不允许供应商返回原始程序包p。
// 如果不允许，则返回仅包含适当错误的新程序包。
func disallowVendor(srcDir string, path string, importerPath string, p *Package, stk *ImportStack) *Package {
	// 如果导入路径为空，则我们使用命令行中给定的名称启动
	// 导入，而不是
	// 导入。命令行上列出的任何内容都可以。
	if importerPath == "" {
		return p
	}

	if perr := disallowVendorVisibility(srcDir, p, importerPath, stk); perr != p {
		return perr
	}

	// 像x/vendor/y这样的路径必须作为y导入，而不能作为x/vendor/y导入。
	if i, ok := FindVendor(path); ok {
		perr := *p
		perr.Error = &PackageError{
			ImportStack: stk.Copy(),
			Err:         ImportErrorf(path, "%s must be imported as %s", path, path[i+len("vendor/"):]),
		}
		perr.Incomplete = true
		return &perr
	}

	return p
}

// 禁止vendorVisibility检查srcDir是否允许导入p。
// 规则与/internal/相同，只是以/vendor 
// 结尾的路径不受规则约束，只受vendor子目录的约束。
// 这允许人们拥有名为vendor、
// 的包和命令，以最大限度地兼容现有的源代码树。
func disallowVendorVisibility(srcDir string, p *Package, importerPath string, stk *ImportStack) *Package {
	// 堆栈不包括p.ImportPath。
	// 如果堆栈上没有任何内容，我们将使用命令行中给定的名称启动
	// 而不是
	// 导入。命令行上列出的任何内容都可以。
	if importerPath == "" {
		return p
	}

	// 检查“供应商”元素。
	i, ok := FindVendor(p.ImportPath)
	if !ok {
		return p
	}

	// 供应商在场。
	// 将导入路径映射回对应于供应商父级的目录。
	if i > 0 {
		i-- // 在“../vendor”中用斜杠倒带
	}
	truncateTo := i + len(p.Dir) - len(p.ImportPath)
	if truncateTo < 0 || len(p.Dir) < truncateTo {
		return p
	}
	parent := p.Dir[:truncateTo]
	if str.HasFilePathPrefix(filepath.Clean(srcDir), filepath.Clean(parent)) {
		return p
	}

	// 在报告错误之前查找符号链接。
	srcDir = expandPath(srcDir)
	parent = expandPath(parent)
	if str.HasFilePathPrefix(filepath.Clean(srcDir), filepath.Clean(parent)) {
		return p
	}

	// 供应商在场，srcDir在父目录树之外。不允许。
	perr := *p
	perr.Error = &PackageError{
		ImportStack: stk.Copy(),
		Err:         errors.New("use of vendored package not allowed"),
	}
	perr.Incomplete = true
	return &perr
}

// FindVendor在给定的导入路径中查找最后一个非终止的“vendor”路径元素。
// 如果没有，FindVendor返回ok=false。
// 否则，FindVendor返回ok=true和“供应商”的索引。
// 
// 请注意，终止“vendor”元素不算：“x/vendor”是它自己的包，而不是导入“”的供应商副本（空导入路径）。
// 这将允许人们使用名为vendor的包或命令。
// 这可能有助于减少破损，或者只是让人困惑。我们拭目以待。
func FindVendor(path string) (index int, ok bool) {
	// 两种情况，取决于字符串开头是否有内部。
	// 顺序很重要：我们必须返回最后一个元素的索引，因为最后一个是有效导入路径的起点。
	switch {
	case strings.Contains(path, "/vendor/"):
		return strings.LastIndex(path, "/vendor/") + 1, true
	case strings.HasPrefix(path, "vendor/"):
		return 0, true
	}
	return 0, false
}

type TargetDir int

const (
	ToTool    TargetDir = iota // 到GOROOT/pkg/tool（默认为cmd/*）
	ToBin                      // 到包根内的bin dir（默认为非cmd/*）
	StalePath                  // 旧的导入路径；无法生成
)

// InstallTargetDir报告用于安装命令p的目标目录。
func InstallTargetDir(p *Package) TargetDir {
	if strings.HasPrefix(p.ImportPath, "code.google.com/p/go.tools/cmd/") {
		return StalePath
	}
	if p.Goroot && strings.HasPrefix(p.ImportPath, "cmd/") && p.Name == "main" {
		switch p.ImportPath {
		case "cmd/go", "cmd/gofmt":
			return ToBin
		}
		return ToTool
	}
	return ToBin
}

var cgoExclude = map[string]bool{
	"runtime/cgo": true,
}

var cgoSyscallExclude = map[string]bool{
	"runtime/cgo":  true,
	"runtime/race": true,
	"runtime/msan": true,
	"runtime/asan": true,
}

var foldPath = make(map[string]string)

// exeFromImportPath为使用导入路径的包返回可执行文件名
// 。
// 
// 可执行文件名是导入路径的最后一个元素。
// 在模块感知模式下，在导入路径上使用附加规则
// 由两个或多个路径元素组成。如果最后一个元素是
// 指定主要版本的vN path元素，则使用导入路径的第二个最后一个元素
// 来代替。
func (p *Package) exeFromImportPath() string {
	_, elem := pathpkg.Split(p.ImportPath)
	if cfg.ModulesEnabled {
		// 如果这是一个例子。com/mycmd/v2，
		// 将其安装为mycmd比安装为v2更有用。见戈朗。org/issue/24667。
		if elem != p.ImportPath && isVersionElement(elem) {
			_, elem = pathpkg.Split(pathpkg.Dir(p.ImportPath))
		}
	}
	return elem
}

// exeFromFiles返回包的可执行文件名
// 使用GoFiles或CgoFiles集合中不带前缀的第一个元素。
// 
// 如果集合为空，则返回空字符串。
func (p *Package) exeFromFiles() string {
	var src string
	if len(p.GoFiles) > 0 {
		src = p.GoFiles[0]
	} else if len(p.CgoFiles) > 0 {
		src = p.CgoFiles[0]
	} else {
		return ""
	}
	_, elem := filepath.Split(src)
	return elem[:len(elem)-len(".go")]
}

// DefaultExecName返回包的默认可执行文件名
func (p *Package) DefaultExecName() string {
	if p.Internal.CmdlineFiles {
		return p.exeFromFiles()
	}
	return p.exeFromImportPath()
}

// load使用来自bp的信息填充p，err，这应该是调用build的结果。上下文进口
// stk包含导入堆栈，不包括路径本身。
func (p *Package) load(ctx context.Context, opts PackageOpts, path string, stk *ImportStack, importPos []token.Position, bp *build.Package, err error) {
	p.copyBuild(opts, bp)

	// localPrefix是我们解释的路径。/相对于
	// 如果我们完全支持它们（不是在模块模式下！）。
	// 合成的主包有时会覆盖此功能。
	if p.Internal.Local && !cfg.ModulesEnabled {
		p.Internal.LocalPrefix = dirToImportPath(p.Dir)
	}

	// setError设置p.如果尚未设置，则设置错误。我们可能会在遇到一些错误后继续进行
	// 以便“go list-e”有更完整的
	// 输出。如果有不止一个错误，我们应该报告第一个错误。
	setError := func(err error) {
		if p.Error == nil {
			p.Error = &PackageError{
				ImportStack: stk.Copy(),
				Err:         err,
			}

			// 如果存在进口货位，则添加进口商的货位信息，并且
			// 正在检查的当前包装是进口商。
			// 如果我们还没有将包p接受到导入堆栈中，
			// 那么错误的原因不在p本身：错误
			// 必须位于显式命令行参数
			// 中，或者位于导入方（由非空的导入操作指示）。
			if path != stk.Top() && len(importPos) > 0 {
				p.Error.setPos(importPos)
			}
		}
	}

	if err != nil {
		p.Incomplete = true
		p.setLoadPackageDataError(err, path, stk, importPos)
	}

	useBindir := p.Name == "main"
	if !p.Standard {
		switch cfg.BuildBuildmode {
		case "c-archive", "c-shared", "plugin":
			useBindir = false
		}
	}

	if useBindir {
		// 当旧代码被删除时，报告一个错误。谷歌。com/p/go。使用工具路径。
		if InstallTargetDir(p) == StalePath {
			// TODO（matloob）：删除此分支，并删除StalePath本身。密码谷歌。com/p/go太老了，即使是检查它的代码现在也过时了！
			newPath := strings.Replace(p.ImportPath, "code.google.com/p/go.", "golang.org/x/", 1)
			e := ImportErrorf(p.ImportPath, "the %v command has moved; use %v instead.", p.ImportPath, newPath)
			setError(e)
			return
		}
		elem := p.DefaultExecName()
		full := cfg.BuildContext.GOOS + "_" + cfg.BuildContext.GOARCH + "/" + elem
		if cfg.BuildContext.GOOS != base.ToolGOOS || cfg.BuildContext.GOARCH != base.ToolGOARCH {
			// 将交叉编译的二进制文件安装到bin的子目录中。
			elem = full
		}
		if p.Internal.Build.BinDir == "" && cfg.ModulesEnabled {
			p.Internal.Build.BinDir = modload.BinDir()
		}
		if p.Internal.Build.BinDir != "" {
			// 安装到GOPATH条目的GOBIN或bin。
			p.Target = filepath.Join(p.Internal.Build.BinDir, elem)
			if !p.Goroot && strings.Contains(elem, "/") && cfg.GOBIN != "" {
				// 不要创建$GOBIN/goos_goarch/elem。
				p.Target = ""
				p.Internal.GobinSubdir = true
			}
		}
		if InstallTargetDir(p) == ToTool {
			// 这是“go tool”的缩写。
			// 覆盖所有常用逻辑，并将其强制放入工具目录。
			if cfg.BuildToolchainName == "gccgo" {
				p.Target = filepath.Join(base.ToolDir, elem)
			} else {
				p.Target = filepath.Join(cfg.GOROOTpkg, "tool", full)
			}
		}
		if p.Target != "" && cfg.BuildContext.GOOS == "windows" {
			p.Target += ".exe"
		}
	} else if p.Internal.Local {
		// 本地导入变为绝对路径。
		// 没有永久安装目标。
		p.Target = ""
	} else {
		p.Target = p.Internal.Build.PkgObj
		if cfg.BuildLinkshared && p.Target != "" {
			// TODO（bcmills）：对p.Target的依赖意味着-linkshared不适用于任何缺少目标的包，例如模块模式下的非主
			// 包。我们可能应该解决这个问题。
			shlibnamefile := p.Target[:len(p.Target)-2] + ".shlibname"
			shlib, err := os.ReadFile(shlibnamefile)
			if err != nil && !os.IsNotExist(err) {
				base.Fatalf("reading shlibname: %v", err)
			}
			if err == nil {
				libname := strings.TrimSpace(string(shlib))
				if cfg.BuildContext.Compiler == "gccgo" {
					p.Shlib = filepath.Join(p.Internal.Build.PkgTargetRoot, "shlibs", libname)
				} else {
					p.Shlib = filepath.Join(p.Internal.Build.PkgTargetRoot, libname)
				}
			}
		}
	}

	// 构建增强的导入列表以添加隐式依赖项。
	// 小心不要添加两次导入，以免混淆。
	importPaths := p.Imports
	addImport := func(path string, forCompiler bool) {
		for _, p := range importPaths {
			if path == p {
				return
			}
		}
		importPaths = append(importPaths, path)
		if forCompiler {
			p.Internal.CompiledImports = append(p.Internal.CompiledImports, path)
		}
	}

	if !opts.IgnoreImports {
		// Cgo translation添加了“不安全”、“运行时/Cgo”和“系统调用”的导入，
		// 除了某些包，以避免循环依赖。
		if p.UsesCgo() {
			addImport("unsafe", true)
		}
		if p.UsesCgo() && (!p.Standard || !cgoExclude[p.ImportPath]) && cfg.BuildContext.Compiler != "gccgo" {
			addImport("runtime/cgo", true)
		}
		if p.UsesCgo() && (!p.Standard || !cgoSyscallExclude[p.ImportPath]) {
			addImport("syscall", true)
		}

		// SWIG增加了一些标准包装的进口。
		if p.UsesSwig() {
			addImport("unsafe", true)
			if cfg.BuildContext.Compiler != "gccgo" {
				addImport("runtime/cgo", true)
			}
			addImport("syscall", true)
			addImport("sync", true)

			// TODO:The。大口大口地喝。swigcxx文件可以使用
			// /%go_导入指令导入其他包。
		}

		// 链接器加载隐式依赖项。
		if p.Name == "main" && !p.Internal.ForceLibrary {
			for _, dep := range LinkerDeps(p) {
				addImport(dep, false)
			}
		}
	}

	// 检查导入路径是否存在不区分大小写的冲突。
	fold := str.ToFold(p.ImportPath)
	if other := foldPath[fold]; other == "" {
		foldPath[fold] = p.ImportPath
	} else if other != p.ImportPath {
		setError(ImportErrorf(p.ImportPath, "case-insensitive import collision: %q and %q", p.ImportPath, other))
		return
	}

	if !SafeArg(p.ImportPath) {
		setError(ImportErrorf(p.ImportPath, "invalid import path %q", p.ImportPath))
		return
	}

	// 此点之后的错误是由此程序包引起的，而不是导入的
	// 程序包。按此处的路径可以防止我们报告带有导入声明位置的错误
	// 。
	stk.Push(path)
	defer stk.Pop()

	pkgPath := p.ImportPath
	if p.Internal.CmdlineFiles {
		pkgPath = "command-line-arguments"
	}
	if cfg.ModulesEnabled {
		p.Module = modload.PackageModuleInfo(ctx, pkgPath)
	}

	p.EmbedFiles, p.Internal.Embed, err = resolveEmbed(p.Dir, p.EmbedPatterns)
	if err != nil {
		p.Incomplete = true
		setError(err)
		embedErr := err.(*EmbedError)
		p.Error.setPos(p.Internal.Build.EmbedPatternPos[embedErr.Pattern])
	}

	// 检查输入文件是否存在不区分大小写的冲突。
	// 为了避免不区分大小写的文件出现问题，我们拒绝使用任何程序包
	// 在不区分大小写的
	// 比较下，两个不同的输入文件具有相同的名称。
	inputs := p.AllFiles()
	f1, f2 := str.FoldDup(inputs)
	if f1 != "" {
		setError(fmt.Errorf("case-insensitive file name collision: %q and %q", f1, f2))
		return
	}

	// 如果输入文件的第一个字母是ASCII，则必须是字母数字。
	// 这避免了在调用命令、
	// 和其他我们还没有想到的问题时文件变成标志。
	// 此外，_cgo_u文件必须由我们生成，而不是提供。
	// 他们被允许有
	// 目录扫描忽略以，
	// 开头的文件，所以我们无论如何都不应该看到任何cgo_u文件，但请注意安全。
	for _, file := range inputs {
		if !SafeArg(file) || strings.HasPrefix(file, "_cgo_") {
			setError(fmt.Errorf("invalid input file name %q", file))
			return
		}
	}
	if name := pathpkg.Base(p.ImportPath); !SafeArg(name) {
		setError(fmt.Errorf("invalid input directory name %q", name))
		return
	}

	// 构建导入包列表和完整依赖项列表。
	imports := make([]*Package, 0, len(p.Imports))
	for i, path := range importPaths {
		if path == "C" {
			continue
		}
		p1 := LoadImport(ctx, opts, path, p.Dir, p, stk, p.Internal.Build.ImportPos[path], ResolveImport)

		path = p1.ImportPath
		importPaths[i] = path
		if i < len(p.Imports) {
			p.Imports[i] = path
		}

		imports = append(imports, p1)
		if p1.Incomplete {
			p.Incomplete = true
		}
	}
	p.Internal.Imports = imports
	p.collectDeps()
	if p.Error == nil && p.Name == "main" && !p.Internal.ForceLibrary && len(p.DepsErrors) == 0 {
		// TODO（bcmills）：加载VCS元数据可能相当慢。ABCFDG＊＊＊＊＊＊＊／／请考虑将此作为后台GORUTIN，并在实际准备建立包时，或在ABCFDG＊＊＊＊＊＊＊/／实际上需要评估包的元数据是否过时时，检索ABCFDG＊＊＊＊＊＊／／异步结果。
		p.setBuildInfo(opts.LoadVCS)
	}

	// 这是一个假包裹。
	if p.Standard && (p.ImportPath == "unsafe" || cfg.BuildContext.Compiler == "gccgo") {
		p.Target = ""
	}

	// 如果未启用cgo，请忽略cgo支持源
	// 就像我们忽略包含导入“C”的go文件一样。
	if !cfg.BuildContext.CgoEnabled {
		p.CFiles = nil
		p.CXXFiles = nil
		p.MFiles = nil
		p.SwigFiles = nil
		p.SwigCXXFiles = nil
		// 请注意，SFiles是正常的（它们会被go汇编器使用）
		// 而HFiles是正常的（它们可能会被SFiles使用）。
		// 系统文件也可以（它们可能不包含对象
		// 代码；请参阅第16050期）。
	}

	// gc工具链只允许带有cgo或SWIG的C源文件。ABCFDG
	if len(p.CFiles) > 0 && !p.UsesCgo() && !p.UsesSwig() && cfg.BuildContext.Compiler == "gc" {
		setError(fmt.Errorf("C source files not allowed when not using cgo or SWIG: %s", strings.Join(p.CFiles, " ")))
		return
	}

	if len(p.CXXFiles) > 0 && !p.UsesCgo() && !p.UsesSwig() {
		setError(fmt.Errorf("C++ source files not allowed when not using cgo or SWIG: %s", strings.Join(p.CXXFiles, " ")))
		return
	}
	if len(p.MFiles) > 0 && !p.UsesCgo() && !p.UsesSwig() {
		setError(fmt.Errorf("Objective-C source files not allowed when not using cgo or SWIG: %s", strings.Join(p.MFiles, " ")))
		return
	}
	if len(p.FFiles) > 0 && !p.UsesCgo() && !p.UsesSwig() {
		setError(fmt.Errorf("Fortran source files not allowed when not using cgo or SWIG: %s", strings.Join(p.FFiles, " ")))
		return
	}
}

// 嵌入错误表示go:embed指令存在问题。
type EmbedError struct {
	Pattern string
	Err     error
}

func (e *EmbedError) Error() string {
	return fmt.Sprintf("pattern %s: %v", e.Pattern, e.Err)
}

func (e *EmbedError) Unwrap() error {
	return e.Err
}

// 解析嵌入解析
// 为便于go mod供应商查找嵌入文件，应将其复制到
// 供应商目录中。
// TODO（#42504）：一旦go mod供应商使用load。PackagesAndErrors，只需
// 呼叫（*Package）。ResolveEmbed 
func ResolveEmbed(dir string, patterns []string) ([]string, error) {
	files, _, err := resolveEmbed(dir, patterns)
	return files, err
}

// ResolveEmbed resolves
// 将文件设置为匹配的唯一文件列表（用于go列表），
// 并将pmap设置为从
// 模式到文件的更精确映射。
func resolveEmbed(pkgdir string, patterns []string) (files []string, pmap map[string][]string, err error) {
	var pattern string
	defer func() {
		if err != nil {
			err = &EmbedError{
				Pattern: pattern,
				Err:     err,
			}
		}
	}()

	// TODO（rsc）：所有这些消息都需要位置信息，以便更好地报告错误。
	pmap = make(map[string][]string)
	have := make(map[string]int)
	dirOK := make(map[string]bool)
	pid := 0 // 模式ID，以允许重用have map 
	for _, pattern = range patterns {
		pid++

		glob := pattern
		all := strings.HasPrefix(pattern, "all:")
		if all {
			glob = pattern[len("all:"):]
		}
		// 检查模式是否对
		if _, err := path.Match(glob, ""); err != nil || !validEmbedPattern(glob) {
			return nil, nil, fmt.Errorf("invalid pattern syntax")
		}

		// Glob有效以查找匹配项。
		match, err := fsys.Glob(pkgdir + string(filepath.Separator) + filepath.FromSlash(glob))
		if err != nil {
			return nil, nil, err
		}

		// 将匹配项列表向下过滤，直到在
		// 将目录打包为一个模块时仍然存在的匹配项。（如果p.Dir在模块缓存中，
		// 只有那些文件已经存在，但是如果p.Dir在当前模块中，
		// 那么可能还有其他东西，比如符号链接或.git目录。）
		var list []string
		for _, file := range match {
			rel := filepath.ToSlash(file[len(pkgdir)+1:]) // 文件，相对于p.Dir 

			what := "file"
			info, err := fsys.Lstat(file)
			if err != nil {
				return nil, nil, err
			}
			if info.IsDir() {
				what = "directory"
			}

			// 检查路径上的目录是否没有开始新模块
			// （不包含go.mod）。
			for dir := file; len(dir) > len(pkgdir)+1 && !dirOK[dir]; dir = filepath.Dir(dir) {
				if _, err := fsys.Stat(filepath.Join(dir, "go.mod")); err == nil {
					return nil, nil, fmt.Errorf("cannot embed %s %s: in different module", what, rel)
				}
				if dir != file {
					if info, err := fsys.Lstat(dir); err == nil && !info.IsDir() {
						return nil, nil, fmt.Errorf("cannot embed %s %s: in non-directory %s", what, rel, dir[len(pkgdir)+1:])
					}
				}
				dirOK[dir] = true
				if elem := filepath.Base(dir); isBadEmbedName(elem) {
					if dir == file {
						return nil, nil, fmt.Errorf("cannot embed %s %s: invalid name %s", what, rel, elem)
					} else {
						return nil, nil, fmt.Errorf("cannot embed %s %s: in invalid directory %s", what, rel, elem)
					}
				}
			}

			switch {
			default:
				return nil, nil, fmt.Errorf("cannot embed irregular file %s", rel)

			case info.Mode().IsRegular():
				if have[rel] != pid {
					have[rel] = pid
					list = append(list, rel)
				}

			case info.IsDir():
				// 收集命名目录中的所有文件，在模块边界处停止
				// 并忽略不会打包到模块中的文件。
				count := 0
				err := fsys.Walk(file, func(path string, info os.FileInfo, err error) error {
					if err != nil {
						return err
					}
					rel := filepath.ToSlash(path[len(pkgdir)+1:])
					name := info.Name()
					if path != file && (isBadEmbedName(name) || ((name[0] == '.' || name[0] == '_') && !all)) {
						// 忽略坏名字，假设它们不会进入模块。
						// 还要避免用户可能不知道的隐藏文件。
						// 见戈朗。org/issue/42328。
						if info.IsDir() {
							return fs.SkipDir
						}
						return nil
					}
					if info.IsDir() {
						if _, err := fsys.Stat(filepath.Join(path, "go.mod")); err == nil {
							return filepath.SkipDir
						}
						return nil
					}
					if !info.Mode().IsRegular() {
						return nil
					}
					count++
					if have[rel] != pid {
						have[rel] = pid
						list = append(list, rel)
					}
					return nil
				})
				if err != nil {
					return nil, nil, err
				}
				if count == 0 {
					return nil, nil, fmt.Errorf("cannot embed directory %s: contains no embeddable files", rel)
				}
			}
		}

		if len(list) == 0 {
			return nil, nil, fmt.Errorf("no matching files found")
		}
		sort.Strings(list)
		pmap[pattern] = list
	}

	for file := range have {
		files = append(files, file)
	}
	sort.Strings(files)
	return files, pmap, nil
}

func validEmbedPattern(pattern string) bool {
	return pattern != "." && fs.ValidPath(pattern)
}

// isBadEmbedName报告文件名是否是
// 不能或不会包含在模块中的文件的基本名称，因此不应将
// 视为已存在用于嵌入。
func isBadEmbedName(name string) bool {
	if err := module.CheckFilePath(name); err != nil {
		return true
	}
	switch name {
	// 空字符串应该是不可能的，但要让它变糟。
	case "":
		return true
	// 模块中不存在版本控制目录。
	case ".bzr", ".hg", ".git", ".svn":
		return true
	}
	return false
}

// collectedeps通过迭代
// p.Internal来填充p.Deps和p.depserror。进口。
// 
// TODO（jayconrod）：collectDeps对每个
// 包的可传递导入进行迭代。我们只需要访问直接进口。
func (p *Package) collectDeps() {
	deps := make(map[string]*Package)
	var q []*Package
	q = append(q, p.Internal.Imports...)
	for i := 0; i < len(q); i++ {
		p1 := q[i]
		path := p1.ImportPath
		// 相同的导入路径是否会产生错误，
		// 取决于尝试导入的内容。
		// 希望记录有错误的条目，以便我们可以报告它们。
		p0 := deps[path]
		if p0 == nil || p1.Error != nil && (p0.Error == nil || len(p0.Error.ImportStack) > len(p1.Error.ImportStack)) {
			deps[path] = p1
			for _, p2 := range p1.Internal.Imports {
				if deps[p2.ImportPath] != p2 {
					q = append(q, p2)
				}
			}
		}
	}

	p.Deps = make([]string, 0, len(deps))
	for dep := range deps {
		p.Deps = append(p.Deps, dep)
	}
	sort.Strings(p.Deps)
	for _, dep := range p.Deps {
		p1 := deps[dep]
		if p1 == nil {
			panic("impossible: missing entry in package cache for " + dep + " imported by " + p.ImportPath)
		}
		if p1.Error != nil {
			p.DepsErrors = append(p.DepsErrors, p1.Error)
		}
	}
}

// vcsstatusache将存储库目录（字符串）
// 映射到它们的VCS信息（vcsStatusError）。
var vcsStatusCache par.Cache

// setBuildInfo收集构建信息，将其格式化为字符串，嵌入二进制文件中，然后设置p.Internal。BuildInfo指向该字符串。
// setBuildInfo应该只在主包上调用，没有错误。
// 
// 可以使用debug检索此信息。ReadBuildInfo。
// 
// 请注意，此处未设置GoVersion字段，以避免对其进行两次编码。
// 由于历史原因，它被单独存储在二进制文件中。
func (p *Package) setBuildInfo(includeVCS bool) {
	// TODO:GOROOT中的可执行文件未嵌入构建和vcs信息。
	// cmd/dist默认使用-gcflags=all=-ldflags=all=这意味着除非用户设置相同的标志，否则这些
	// 可执行文件总是过时的。
	// 在未设置GO_GCFLAGS和GO_LDFLAGS 
	// 时，省略这些标志可能是安全的？
	setPkgErrorf := func(format string, args ...any) {
		if p.Error == nil {
			p.Error = &PackageError{Err: fmt.Errorf(format, args...)}
		}
	}

	var debugModFromModinfo func(*modinfo.ModulePublic) *debug.Module
	debugModFromModinfo = func(mi *modinfo.ModulePublic) *debug.Module {
		version := mi.Version
		if version == "" {
			version = "(devel)"
		}
		dm := &debug.Module{
			Path:    mi.Path,
			Version: version,
		}
		if mi.Replace != nil {
			dm.Replace = debugModFromModinfo(mi.Replace)
		} else if mi.Version != "" {
			dm.Sum = modfetch.Sum(module.Version{Path: mi.Path, Version: mi.Version})
		}
		return dm
	}

	var main debug.Module
	if p.Module != nil {
		main = *debugModFromModinfo(p.Module)
	}

	visited := make(map[*Package]bool)
	mdeps := make(map[module.Version]*debug.Module)
	var q []*Package
	q = append(q, p.Internal.Imports...)
	for len(q) > 0 {
		p1 := q[0]
		q = q[1:]
		if visited[p1] {
			continue
		}
		visited[p1] = true
		if p1.Module != nil {
			m := module.Version{Path: p1.Module.Path, Version: p1.Module.Version}
			if p1.Module.Path != main.Path && mdeps[m] == nil {
				mdeps[m] = debugModFromModinfo(p1.Module)
			}
		}
		q = append(q, p1.Internal.Imports...)
	}
	sortedMods := make([]module.Version, 0, len(mdeps))
	for mod := range mdeps {
		sortedMods = append(sortedMods, mod)
	}
	module.Sort(sortedMods)
	deps := make([]*debug.Module, len(sortedMods))
	for i, mod := range sortedMods {
		deps[i] = mdeps[mod]
	}

	pkgPath := p.ImportPath
	if p.Internal.CmdlineFiles {
		pkgPath = "command-line-arguments"
	}
	info := &debug.BuildInfo{
		Path: pkgPath,
		Main: main,
		Deps: deps,
	}
	appendSetting := func(key, value string) {
		value = strings.ReplaceAll(value, "\n", " ") // 使值安全
		info.Settings = append(info.Settings, debug.BuildSetting{Key: key, Value: value})
	}

	// 添加与生成相关的命令行标志。
	// 这是一个信息性的列表，不是详尽的列表。
	// 请保持列表排序。
	if !p.Standard {
		if cfg.BuildASan {
			appendSetting("-asan", "true")
		}
		if BuildAsmflags.present {
			appendSetting("-asmflags", BuildAsmflags.String())
		}
		appendSetting("-compiler", cfg.BuildContext.Compiler)
		if BuildGccgoflags.present && cfg.BuildContext.Compiler == "gccgo" {
			appendSetting("-gccgoflags", BuildGccgoflags.String())
		}
		if BuildGcflags.present && cfg.BuildContext.Compiler == "gc" {
			appendSetting("-gcflags", BuildGcflags.String())
		}
		if BuildLdflags.present {
			appendSetting("-ldflags", BuildLdflags.String())
		}
		if cfg.BuildMSan {
			appendSetting("-msan", "true")
		}
		if cfg.BuildRace {
			appendSetting("-race", "true")
		}
		if tags := cfg.BuildContext.BuildTags; len(tags) > 0 {
			appendSetting("-tags", strings.Join(tags, ","))
		}
		cgo := "0"
		if cfg.BuildContext.CgoEnabled {
			cgo = "1"
		}
		appendSetting("CGO_ENABLED", cgo)
		if cfg.BuildContext.CgoEnabled {
			for _, name := range []string{"CGO_CFLAGS", "CGO_CPPFLAGS", "CGO_CXXFLAGS", "CGO_LDFLAGS"} {
				appendSetting(name, cfg.Getenv(name))
			}
		}
		appendSetting("GOARCH", cfg.BuildContext.GOARCH)
		if cfg.GOEXPERIMENT != "" {
			appendSetting("GOEXPERIMENT", cfg.GOEXPERIMENT)
		}
		appendSetting("GOOS", cfg.BuildContext.GOOS)
		if key, val := cfg.GetArchEnv(); key != "" && val != "" {
			appendSetting(key, val)
		}
	}

	// 如果所有条件均为真，则添加VCS状态：
	// 
	// /-已启用buildvcs。
	// /-p是包含在主模块中的非测试（工作区中可能有多个
	// 主模块，但本地替换不计算在内）。
	// -当前目录和p模块的根目录都包含在同一个本地存储库中。
	// /-我们知道获取状态所需的VCS命令。
	setVCSError := func(err error) {
		setPkgErrorf("error obtaining VCS status: %v\n\tUse -buildvcs=false to disable VCS stamping.", err)
	}

	var repoDir string
	var vcsCmd *vcs.Cmd
	var err error
	const allowNesting = true
	if includeVCS && p.Module != nil && p.Module.Version == "" && !p.Standard && !p.IsTestOnly() {
		repoDir, vcsCmd, err = vcs.FromDir(base.Cwd(), "", allowNesting)
		if err != nil && !errors.Is(err, os.ErrNotExist) {
			setVCSError(err)
			return
		}
		if !str.HasFilePathPrefix(p.Module.Dir, repoDir) &&
			!str.HasFilePathPrefix(repoDir, p.Module.Dir) {
			// 包含主包的模块与包含工作目录的存储库不重叠。不要包含风投信息。
			// 如果repo包含模块，反之亦然，但它们不是
			// 同一目录，则可能是错误（见下文）。
			repoDir, vcsCmd = "", nil
		}
	}
	if repoDir != "" && vcsCmd.Status != nil {
		// 检查当前目录、包和模块是否在同一个
		// 存储库。风投。FromDir允许嵌套Git存储库，但其他VCS工具不允许嵌套
		// 。当前目录可能在
		// p.Module之外。使用工作区时使用Dir。
		pkgRepoDir, _, err := vcs.FromDir(p.Dir, "", allowNesting)
		if err != nil {
			setVCSError(err)
			return
		}
		if pkgRepoDir != repoDir {
			setVCSError(fmt.Errorf("main package is in repository %q but current directory is in repository %q", pkgRepoDir, repoDir))
			return
		}
		modRepoDir, _, err := vcs.FromDir(p.Module.Dir, "", allowNesting)
		if err != nil {
			setVCSError(err)
			return
		}
		if modRepoDir != repoDir {
			setVCSError(fmt.Errorf("main module is in repository %q but current directory is in repository %q", modRepoDir, repoDir))
			return
		}

		type vcsStatusError struct {
			Status vcs.Status
			Err    error
		}
		cached := vcsStatusCache.Do(repoDir, func() any {
			st, err := vcsCmd.Status(vcsCmd, repoDir)
			return vcsStatusError{st, err}
		}).(vcsStatusError)
		if err := cached.Err; err != nil {
			setVCSError(err)
			return
		}
		st := cached.Status

		appendSetting("vcs", vcsCmd.Cmd)
		if st.Revision != "" {
			appendSetting("vcs.revision", st.Revision)
		}
		if !st.CommitTime.IsZero() {
			stamp := st.CommitTime.UTC().Format(time.RFC3339Nano)
			appendSetting("vcs.time", stamp)
		}
		appendSetting("vcs.modified", strconv.FormatBool(st.Uncommitted))
	}

	p.Internal.BuildInfo = info.String()
}

// SafeArg报告arg是否为“安全”命令行参数，这意味着当它出现在命令行中时，除了它自己的名称之外，它可能没有其他特殊含义。
// 显然，以-开头的arg不安全（它们看起来像旗帜）。
// 不太明显的是，以@开头的参数不安全（它们看起来像
// GNU binutils标志文件说明符，有时称为“响应文件”）。
// 为了保守起见，我们几乎拒绝任何以非字母数字ASCII开头的arg。我们接受领导和/或在文件系统路径中。
// cmd/compile/internal/gc/noder中有此函数的副本。去
func SafeArg(name string) bool {
	if name == "" {
		return false
	}
	c := name[0]
	return '0' <= c && c <= '9' || 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || c == '.' || c == '_' || c == '/' || c >= utf8.RuneSelf
}

// Linkedeps返回主程序包p的链接器相关依赖项列表。
func LinkerDeps(p *Package) []string {
	// Everything links runtime。
	deps := []string{"runtime"}

	// 外部链接模式强制导入runtime/cgo。
	if externalLinkingForced(p) && cfg.BuildContext.Compiler != "gccgo" {
		deps = append(deps, "runtime/cgo")
	}
	// 在GOARM=5的ARM上，它强制导入数学，用于软浮点。
	if cfg.Goarch == "arm" {
		deps = append(deps, "math")
	}
	// 使用种族检测器强制导入runtime/race。
	if cfg.BuildRace {
		deps = append(deps, "runtime/race")
	}
	// 使用内存清洗剂强制导入runtime/msan。
	if cfg.BuildMSan {
		deps = append(deps, "runtime/msan")
	}
	// 使用地址消毒器强制导入runtime/asan。
	if cfg.BuildASan {
		deps = append(deps, "runtime/asan")
	}

	return deps
}

// 外部链接强制报告是否正在进行外部链接
// 即使对于不使用cgo的程序也是如此。
func externalLinkingForced(p *Package) bool {
	if !cfg.BuildContext.CgoEnabled {
		return false
	}

	// 有些目标即使在GOROOT内部也必须使用外部链接。
	switch cfg.BuildContext.GOOS {
	case "android":
		if cfg.BuildContext.GOARCH != "arm64" {
			return true
		}
	case "ios":
		return true
	}

	// 当前构建模式c-shared、pie（在不支持内部链接模式的系统上）（当前所有
	// 系统：问题#18968））、插件和-linkshared强制
	// 外部链接模式，当然
	// /-ldflags=-linkmode=external。外部链接模式强制
	// 导入运行时/cgo。
	// 如果有多个链接模式选项，最后一个选项获胜。
	pieCgo := cfg.BuildBuildmode == "pie" && !sys.InternalLinkPIESupported(cfg.BuildContext.GOOS, cfg.BuildContext.GOARCH)
	linkmodeExternal := false
	if p != nil {
		ldflags := BuildLdflags.For(p)
		for i := len(ldflags) - 1; i >= 0; i-- {
			a := ldflags[i]
			if a == "-linkmode=external" ||
				a == "-linkmode" && i+1 < len(ldflags) && ldflags[i+1] == "external" {
				linkmodeExternal = true
				break
			} else if a == "-linkmode=internal" ||
				a == "-linkmode" && i+1 < len(ldflags) && ldflags[i+1] == "internal" {
				break
			}
		}
	}

	return cfg.BuildBuildmode == "c-shared" || cfg.BuildBuildmode == "plugin" || pieCgo || cfg.BuildLinkshared || linkmodeExternal
}

// mkAbs将列表（必须是相对于p.Dir的路径）重写为绝对路径的排序列表。它在适当的位置编辑列表，但对于
// 便利也会将列表返回给调用者。
func (p *Package) mkAbs(list []string) []string {
	for i, f := range list {
		list[i] = filepath.Join(p.Dir, f)
	}
	sort.Strings(list)
	return list
}

// InternalGoFiles返回为包生成的Go文件列表，
// 使用绝对路径。
func (p *Package) InternalGoFiles() []string {
	return p.mkAbs(str.StringList(p.GoFiles, p.CgoFiles, p.TestGoFiles))
}

// InternalXGoFiles返回为XTest包生成的Go文件列表，
// 使用绝对路径。
func (p *Package) InternalXGoFiles() []string {
	return p.mkAbs(p.XTestGoFiles)
}

// InternalGoFiles返回所有可能与包相关的Go文件的列表，
// 使用绝对路径。“可能相关”指的是文件不是因为构建标记而被排除在外，而是名称以开头的文件。或uu仍然被排除在外。
func (p *Package) InternalAllGoFiles() []string {
	return p.mkAbs(str.StringList(p.IgnoredGoFiles, p.GoFiles, p.CgoFiles, p.TestGoFiles, p.XTestGoFiles))
}

// usesSwig报告包是否需要运行SWIG。
func (p *Package) UsesSwig() bool {
	return len(p.SwigFiles) > 0 || len(p.SwigCXXFiles) > 0
}

// usesCgo报告包是否需要运行cgo 
func (p *Package) UsesCgo() bool {
	return len(p.CgoFiles) > 0
}

// PackageList返回dag中以根为根的包列表
// 。
func PackageList(roots []*Package) []*Package {
	seen := map[*Package]bool{}
	all := []*Package{}
	var walk func(*Package)
	walk = func(p *Package) {
		if seen[p] {
			return
		}
		seen[p] = true
		for _, p1 := range p.Internal.Imports {
			walk(p1)
		}
		all = append(all, p)
	}
	for _, root := range roots {
		walk(root)
	}
	return all
}

// TestPackageList返回根目录为根目录的dag中的包列表
// 在深度优先后序遍历中访问，包括根目录的导入。这会忽略测试包中的错误。
func TestPackageList(ctx context.Context, opts PackageOpts, roots []*Package) []*Package {
	seen := map[*Package]bool{}
	all := []*Package{}
	var walk func(*Package)
	walk = func(p *Package) {
		if seen[p] {
			return
		}
		seen[p] = true
		for _, p1 := range p.Internal.Imports {
			walk(p1)
		}
		all = append(all, p)
	}
	walkTest := func(root *Package, path string) {
		var stk ImportStack
		p1 := LoadImport(ctx, opts, path, root.Dir, root, &stk, root.Internal.Build.TestImportPos[path], ResolveImport)
		if p1.Error == nil {
			walk(p1)
		}
	}
	for _, root := range roots {
		walk(root)
		for _, path := range root.TestImports {
			walkTest(root, path)
		}
		for _, path := range root.XTestImports {
			walkTest(root, path)
		}
	}
	return all
}

// LoadImportWithFlags使用给定的导入路径加载包，
// 在该包上设置工具标志。此函数用于加载隐式
// 依赖项（例如用于覆盖的sync/atomic）。
// TODO（jayconrod）：删除此函数并在LoadImport中自动设置标志
// 。
func LoadImportWithFlags(path, srcDir string, parent *Package, stk *ImportStack, importPos []token.Position, mode int) *Package {
	p := LoadImport(context.TODO(), PackageOpts{}, path, srcDir, parent, stk, importPos, mode)
	setToolFlags(p)
	return p
}

// packagepts控制packagesanderor和其他包的行为
// 加载函数。
type PackageOpts struct {
	// IgnoreImports控制我们在加载包时是否忽略显式和隐式导入
	// 。当支持Cgo 
	// /或SWIG以及链接主包时，会添加隐式导入。
	IgnoreImports bool

	// ModResolveTests指示对模块加载器的调用是否也应
	// 解析所请求包的测试依赖关系。wen jian defg
	// 可能会丢失，并解决这些丢失的依赖项
	// 可能会更改提供其他软件包的模块的选定版本。
	ModResolveTests bool

	// 如果调用方只想加载主包，则main only为true。
	// 对于与非主包匹配的文本参数，可能会返回存根
	// 并返回一个错误。对于非文字参数（带“…”），非主程序包
	// 不匹配，可能无法加载它们的依赖项。对于不匹配主包的非文字参数，可能会打印一条警告
	// 。
	MainOnly bool

	// LoadVCS控制我们是否也加载主包的版本控制元数据。
	LoadVCS bool
}

// PackagesAndErrors返回由命令行参数
// “patterns”命名的包。如果无法加载命名包，PackagesAndErrors将返回
// a*包，其中包含描述失败的错误字段。如果在加载导入的包时发现错误
// 将设置DepErrors字段。也可以设置不完整字段
// 。
// 
// 要获得软件包的简单列表，请使用PackageList。
// 要报告加载包时的错误，请使用ReportPackageErrors。
func PackagesAndErrors(ctx context.Context, opts PackageOpts, patterns []string) []*Package {
	ctx, span := trace.StartSpan(ctx, "load.PackagesAndErrors")
	defer span.Done()

	for _, p := range patterns {
		// 列表仅在所有模式下受支持，这些模式指的是属于同一目录的文件：
		// 。
		// /-显式包路径或模式。
		if strings.HasSuffix(p, ".go") {
			// 我们需要测试路径是否是实际的Go文件，而不是
			// 以“”结尾的包路径或模式。go”（见golang.org/issue/34653）。
			if fi, err := fsys.Stat(p); err == nil && !fi.IsDir() {
				return []*Package{GoFilesPackage(ctx, opts, patterns)}
			}
		}
	}

	var matches []*search.Match
	if modload.Init(); cfg.ModulesEnabled {
		modOpts := modload.PackageOpts{
			ResolveMissingImports: true,
			LoadTests:             opts.ModResolveTests,
			SilencePackageErrors:  true,
		}
		matches, _ = modload.LoadPackages(ctx, modOpts, patterns...)
	} else {
		noModRoots := []string{}
		matches = search.ImportPaths(patterns, noModRoots)
	}

	var (
		pkgs    []*Package
		stk     ImportStack
		seenPkg = make(map[*Package]bool)
	)

	pre := newPreload()
	defer pre.flush()
	pre.preloadMatches(ctx, opts, matches)

	for _, m := range matches {
		for _, pkg := range m.Pkgs {
			if pkg == "" {
				panic(fmt.Sprintf("ImportPaths returned empty package for pattern %s", m.Pattern()))
			}
			p := loadImport(ctx, opts, pre, pkg, base.Cwd(), nil, &stk, nil, 0)
			p.Match = append(p.Match, m.Pattern())
			p.Internal.CmdlinePkg = true
			if m.IsLiteral() {
				// 注意：不要设置=m.IsLiteral无条件
				// 因为我们可能会看到p同时匹配
				// 一个文本模式和一个非文本模式。
				p.Internal.CmdlinePkgLiteral = true
			}
			if seenPkg[p] {
				continue
			}
			seenPkg[p] = true
			pkgs = append(pkgs, p)
		}

		if len(m.Errs) > 0 {
			// 除了从
			// 模式中实际解析的任何包之外，解析模式本身也存在一些错误。
			// 将其作为合成包报告。
			p := new(Package)
			p.ImportPath = m.Pattern()
			// 传递一个空的ImportStack和nil importPos：错误是由模式而不是导入引起的。
			var stk ImportStack
			var importPos []token.Position
			p.setLoadPackageDataError(m.Errs[0], m.Pattern(), &stk, importPos)
			p.Incomplete = true
			p.Match = append(p.Match, m.Pattern())
			p.Internal.CmdlinePkg = true
			if m.IsLiteral() {
				p.Internal.CmdlinePkgLiteral = true
			}
			pkgs = append(pkgs, p)
		}
	}

	if opts.MainOnly {
		pkgs = mainPackagesOnly(pkgs, matches)
	}

	// CmdlinePkg设置正确后，
	// 计算所有加载包的有效标志
	// （不仅是与模式匹配的包，还包括其依赖项
	// ）。
	setToolFlags(pkgs...)

	return pkgs
}

// CheckPackageErrors打印加载PKG及其依赖项时遇到的错误
// 如果发现任何错误，将以非零状态退出。
func CheckPackageErrors(pkgs []*Package) {
	printed := map[*PackageError]bool{}
	for _, pkg := range pkgs {
		if pkg.Error != nil {
			base.Errorf("%v", pkg.Error)
			printed[pkg.Error] = true
		}
		for _, err := range pkg.DepsErrors {
			// 由于这些是依赖项中的错误，
			// 同一个错误可能会出现多次，
			// 在依赖它的每个包中出现一次。
			// 每次只打印一次。
			if !printed[err] {
				printed[err] = true
				base.Errorf("%v", err)
			}
		}
	}
	base.ExitIfErrors()

	// 检查同一包裹是否有重复装载。
	// 这应该是不可能的，但如果真的发生了，那么我们最终会尝试构建同一个包两次，
	// 通常并行覆盖相同的文件，
	// 这不太好用。
	seen := map[string]bool{}
	reported := map[string]bool{}
	for _, pkg := range PackageList(pkgs) {
		if seen[pkg.ImportPath] && !reported[pkg.ImportPath] {
			reported[pkg.ImportPath] = true
			base.Errorf("internal error: duplicate loads of %s", pkg.ImportPath)
		}
		seen[pkg.ImportPath] = true
	}
	base.ExitIfErrors()
}

// main PackagesOnly筛选出仅由参数匹配的非主包
// 包含“…”并返回剩余的主包。
// 
// 名称缺失、无效或不明确的包可能会被视为
// 可能是主包。
// 
// main Packages仅设置非主程序包的错误字段，如果
// 由文字参数命名，则返回该字段。
// 
// main PackagesOnly为仅与
// 非main packages匹配的非文字参数打印警告。
func mainPackagesOnly(pkgs []*Package, matches []*search.Match) []*Package {
	treatAsMain := map[string]bool{}
	for _, m := range matches {
		if m.IsLiteral() {
			for _, path := range m.Pkgs {
				treatAsMain[path] = true
			}
		}
	}

	var mains []*Package
	for _, pkg := range pkgs {
		if pkg.Name == "main" {
			treatAsMain[pkg.ImportPath] = true
			mains = append(mains, pkg)
			continue
		}

		if len(pkg.InvalidGoFiles) > 0 { // 待办事项（#45999）：&打包。Name==“”，但当前go/build设置pkg。如果模棱两可，可以任意命名。
			// 这个包有（或可能有）相互冲突的名称，我们很难判断其中是否有一个是“main”。所以假设它可能是，并且
			// 为包报告一个错误。
			treatAsMain[pkg.ImportPath] = true
		}
		if treatAsMain[pkg.ImportPath] {
			if pkg.Error == nil {
				pkg.Error = &PackageError{Err: &mainPackageError{importPath: pkg.ImportPath}}
			}
			mains = append(mains, pkg)
		}
	}

	for _, m := range matches {
		if m.IsLiteral() || len(m.Pkgs) == 0 {
			continue
		}
		foundMain := false
		for _, path := range m.Pkgs {
			if treatAsMain[path] {
				foundMain = true
				break
			}
		}
		if !foundMain {
			fmt.Fprintf(os.Stderr, "go: warning: %q matched only non-main packages\n", m.Pattern())
		}
	}

	return mains
}

type mainPackageError struct {
	importPath string
}

func (e *mainPackageError) Error() string {
	return fmt.Sprintf("package %s is not a main package", e.importPath)
}

func (e *mainPackageError) ImportPath() string {
	return e.importPath
}

func setToolFlags(pkgs ...*Package) {
	for _, p := range PackageList(pkgs) {
		p.Internal.Asmflags = BuildAsmflags.For(p)
		p.Internal.Gcflags = BuildGcflags.For(p)
		p.Internal.Ldflags = BuildLdflags.For(p)
		p.Internal.Gccgoflags = BuildGccgoflags.For(p)
	}
}

// GoFilesPackage创建一个用于构建Go文件集合的包
// （通常在命令行上命名）。目标为
// p包命名为p.a，或以主包的第一个Go文件命名。
func GoFilesPackage(ctx context.Context, opts PackageOpts, gofiles []string) *Package {
	modload.Init()

	for _, f := range gofiles {
		if !strings.HasSuffix(f, ".go") {
			pkg := new(Package)
			pkg.Internal.Local = true
			pkg.Internal.CmdlineFiles = true
			pkg.Name = f
			pkg.Error = &PackageError{
				Err: fmt.Errorf("named files must be .go files: %s", pkg.Name),
			}
			return pkg
		}
	}

	var stk ImportStack
	ctxt := cfg.BuildContext
	ctxt.UseAllFiles = true

	// 合成只显示命名文件的假“目录”，
	// 使其看起来像是一个标准包或
	// 命令目录。为了使本地导入一致地解析
	// 文件，所有文件必须位于同一目录中。
	var dirent []fs.FileInfo
	var dir string
	for _, file := range gofiles {
		fi, err := fsys.Stat(file)
		if err != nil {
			base.Fatalf("%s", err)
		}
		if fi.IsDir() {
			base.Fatalf("%s is a directory, should be a Go file", file)
		}
		dir1 := filepath.Dir(file)
		if dir == "" {
			dir = dir1
		} else if dir != dir1 {
			base.Fatalf("named files must all be in one directory; have %s and %s", dir, dir1)
		}
		dirent = append(dirent, fi)
	}
	ctxt.ReadDir = func(string) ([]fs.FileInfo, error) { return dirent, nil }

	if cfg.ModulesEnabled {
		modload.ImportFromFiles(ctx, gofiles)
	}

	var err error
	if dir == "" {
		dir = base.Cwd()
	}
	dir, err = filepath.Abs(dir)
	if err != nil {
		base.Fatalf("%s", err)
	}

	bp, err := ctxt.ImportDir(dir, 0)
	pkg := new(Package)
	pkg.Internal.Local = true
	pkg.Internal.CmdlineFiles = true
	pkg.load(ctx, opts, "command-line-arguments", &stk, nil, bp, err)
	if !cfg.ModulesEnabled {
		pkg.Internal.LocalPrefix = dirToImportPath(dir)
	}
	pkg.ImportPath = "command-line-arguments"
	pkg.Target = ""
	pkg.Match = gofiles

	if pkg.Name == "main" {
		exe := pkg.DefaultExecName() + cfg.ExeSuffix

		if cfg.GOBIN != "" {
			pkg.Target = filepath.Join(cfg.GOBIN, exe)
		} else if cfg.ModulesEnabled {
			pkg.Target = filepath.Join(modload.BinDir(), exe)
		}
	}

	if opts.MainOnly && pkg.Name != "main" && pkg.Error == nil {
		pkg.Error = &PackageError{Err: &mainPackageError{importPath: pkg.ImportPath}}
	}
	setToolFlags(pkg)

	return pkg
}

// PackagesAndErrorsOutsideModule与PackagesAndErrors类似，但在
// 模块感知模式下运行，并忽略go。当前目录中的mod文件或任何
// 父目录，如果有。这用于“go 
// 安装”的实现pkg@version'和其他支持类似形式的命令。
// 
// modload。ForceUseModules必须为true，并且modload。在调用此函数之前，RootMode必须为NoRoot 
// 。
// 
// PackagesAndErrorsOutsideModule施加了若干约束，以避免
// 歧义。所有参数必须具有相同的版本后缀（而不仅仅是解析为同一版本的后缀
// ）。它们必须引用同一
// 模块中的包，该模块不能是std或cmd。该模块不被认为是主要的
// 模块，而是它的go。mod file（如果有）不能包含指令，如果它是主模块
// 将导致对其进行不同的解释。
// （替换、排除），
func PackagesAndErrorsOutsideModule(ctx context.Context, opts PackageOpts, args []string) ([]*Package, error) {
	if !modload.ForceUseModules {
		panic("modload.ForceUseModules must be true")
	}
	if modload.RootMode != modload.NoRoot {
		panic("modload.RootMode must be NoRoot")
	}

	// 检查参数是否满足语法约束。
	var version string
	for _, arg := range args {
		if i := strings.Index(arg, "@"); i >= 0 {
			version = arg[i+1:]
			if version == "" {
				return nil, fmt.Errorf("%s: version must not be empty", arg)
			}
			break
		}
	}
	patterns := make([]string, len(args))
	for i, arg := range args {
		if !strings.HasSuffix(arg, "@"+version) {
			return nil, fmt.Errorf("%s: all arguments must have the same version (@%s)", arg, version)
		}
		p := arg[:len(arg)-len(version)-1]
		switch {
		case build.IsLocalImport(p):
			return nil, fmt.Errorf("%s: argument must be a package path, not a relative path", arg)
		case filepath.IsAbs(p):
			return nil, fmt.Errorf("%s: argument must be a package path, not an absolute path", arg)
		case search.IsMetaPackage(p):
			return nil, fmt.Errorf("%s: argument must be a package path, not a meta-package", arg)
		case path.Clean(p) != p:
			return nil, fmt.Errorf("%s: argument must be a clean package path", arg)
		case !strings.Contains(p, "...") && search.IsStandardImportPath(p) && goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, p):
			return nil, fmt.Errorf("%s: argument must not be a package in the standard library", arg)
		default:
			patterns[i] = p
		}
	}

	// 查询提供第一个参数的模块，加载其go。mod file和
	// 检查它是否包含会导致其成为
	// 的指令，如果它是主模块，则会有不同的解释。
	// 
	// 如果多个模块匹配第一个参数，则接受最长的匹配
	// （第一个结果）。这个模块可能不会提供以
	// 后面的参数命名的包，其他模块可能会提供。不过，我们还是不要太轻率了。
	allowed := modload.CheckAllowed
	if modload.IsRevisionQuery(version) {
		// 如果要求进行特定修订，请不要检查是否有撤回。
		allowed = nil
	}
	noneSelected := func(path string) (version string) { return "none" }
	qrs, err := modload.QueryPackages(ctx, patterns[0], version, noneSelected, allowed)
	if err != nil {
		return nil, fmt.Errorf("%s: %w", args[0], err)
	}
	rootMod := qrs[0].Mod
	data, err := modfetch.GoMod(rootMod.Path, rootMod.Version)
	if err != nil {
		return nil, fmt.Errorf("%s: %w", args[0], err)
	}
	f, err := modfile.Parse("go.mod", data, nil)
	if err != nil {
		return nil, fmt.Errorf("%s (in %s): %w", args[0], rootMod, err)
	}
	directiveFmt := "%s (in %s):\n" +
		"\tThe go.mod file for the module providing named packages contains one or\n" +
		"\tmore %s directives. It must not contain directives that would cause\n" +
		"\tit to be interpreted differently than if it were the main module."
	if len(f.Replace) > 0 {
		return nil, fmt.Errorf(directiveFmt, args[0], rootMod, "replace")
	}
	if len(f.Exclude) > 0 {
		return nil, fmt.Errorf(directiveFmt, args[0], rootMod, "exclude")
	}

	// 由于我们处于NoRoot模式，构建列表最初只包含
	// 伪命令行参数模块。在
	// 模块上添加一个需求，该模块提供命令行上命名的包。
	if _, err := modload.EditBuildList(ctx, nil, []module.Version{rootMod}); err != nil {
		return nil, fmt.Errorf("%s: %w", args[0], err)
	}

	// 加载所有参数的包。
	pkgs := PackagesAndErrors(ctx, opts, patterns)

	// 检查命名包是否由同一模块提供。
	for _, pkg := range pkgs {
		var pkgErr error
		if pkg.Module == nil {
			// std、cmd中的包及其供应商依赖项
			// 未设置此字段。
			pkgErr = fmt.Errorf("package %s not provided by module %s", pkg.ImportPath, rootMod)
		} else if pkg.Module.Path != rootMod.Path || pkg.Module.Version != rootMod.Version {
			pkgErr = fmt.Errorf("package %s provided by module %s@%s\n\tAll packages must be provided by the same module (%s).", pkg.ImportPath, pkg.Module.Path, pkg.Module.Version, rootMod)
		}
		if pkgErr != nil && pkg.Error == nil {
			pkg.Error = &PackageError{Err: pkgErr}
		}
	}

	matchers := make([]func(string) bool, len(patterns))
	for i, p := range patterns {
		if strings.Contains(p, "...") {
			matchers[i] = search.MatchPattern(p)
		}
	}
	return pkgs, nil
}
